Lately I wrote a template function to solve some code repetitions. It looks like this:
template<class T, class R, class... Args>
R call_or_throw(const std::weak_ptr<T>& ptr, const std::string& error, R (T::*fun)(Args...), Args... args) {
if (auto sp = ptr.lock())
{
return std::invoke(fun, *sp, args...);
}
else
{
throw std::runtime_error(error.c_str());
}
}
int main() {
auto a = std::make_shared<A>();
call_or_throw(std::weak_ptr<A>(a), "err", &A::foo, 1);
}
This code works perfectly well for class A
which looks like this:
class A {
public:
void foo(int x) {
}
};
But fails to compile for one like this:
class A {
public:
void foo(const int& x) {
}
};
Why is it so (by why I mean why it fails to deduce the type) and how (if it is possible at all) can I make this code work with references? Live example
A template has only one type, but a specialization is needed for pointer, reference, pointer to member, or function pointer types. The specialization itself is still a template on the type pointed to or referenced.
Class Template Argument Deduction (CTAD) is a C++17 Core Language feature that reduces code verbosity. C++17's Standard Library also supports CTAD, so after upgrading your toolset, you can take advantage of this new feature when using STL types like std::pair and std::vector.
Member functions can be function templates in several contexts. All functions of class templates are generic but are not referred to as member templates or member function templates. If these member functions take their own template arguments, they are considered to be member function templates.
Args
types cannot be deduced both as const&
(from fun
parameter declaration) and non-reference from args
declaration. A simple fix is to use two separate template type parameter packs:
template<class T, class R, class... Args, class... DeclaredArgs>
R call_or_throw(
const std::weak_ptr<T>& ptr,
const std::string& error,
R (T::*fun)(DeclaredArgs...),
Args... args);
As a downside, I can imagine slightly longer error messages in case of bad usage.
Note that the template parameter Args
's type is deduced as const int&
on the 3rd function argument &A::foo
, and deduced as int
on the 4th function parameter 1
. They don't match and cause deduction fails.
You can exclude the 4th parameter from deduction, e.g.
template<class T, class R, class... Args>
R call_or_throw(const std::weak_ptr<T>& ptr,
const std::string& error,
R (T::*fun)(Args...),
std::type_identity_t<Args>... args) {
// ^^^^^^^^^^^^^^^^^^^^^^^^^^
LIVE
PS: std::type_identity
is supported since C++20; but it's quite easy to implement one.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With