Why the following does not compile when passing i
to the constructor. The other similar constructs compile.
#include <iostream>
#include <functional>
int RetXPrintU(int x, uint u)
{
std::cout << "RetXprintU(): " << u << std::endl;
return x;
}
template <typename Fn, typename... Args>
void Call(Fn&& fun, Args&&... args)
{
std::invoke(std::forward<Fn>(fun), std::forward<Args>(args)...);
}
template <typename Fn, typename... Args>
class CallableObj
{
public:
explicit CallableObj(Fn&& fun, Args&&... args)
{
std::invoke(std::forward<Fn>(fun), std::forward<Args>(args)...);
}
};
int main() {
int i = 4;
std::invoke(RetXPrintU, i, 8u);
Call(RetXPrintU, i, 8u);
CallableObj co(RetXPrintU, i, 8u); // WHY I DO NOT COMPILE?
//CallableObj co(RetXPrintU, 0, 8u); // WHY I COMPILE?
return 0;
}
The issue here is you need to move the template from the class to the constructor. When you have
template <typename Fn, typename... Args>
class CallableObj
{
public:
explicit CallableObj(Fn&& fun, Args&&... args)
{
std::invoke(std::forward<Fn>(fun), std::forward<Args>(args)...);
}
};
Args&&...
is not a variadic forwarding reference because it is deduced for the class itself and when you call CallableObj co(RetXPrintU, i, 8u);
the class is instantiated and the constructor gets stamped out as
explicit CallableObj(int(int, unsigned int)&& fun, int&& arg1, unsigned int&& arg2)
What we want is
class CallableObj
{
public:
template <typename Fn, typename... Args>
explicit CallableObj(Fn&& fun, Args&&... args)
{
std::invoke(std::forward<Fn>(fun), std::forward<Args>(args)...);
}
};
so that now Args
will be deduced when the constructor is called and Args&&
is now a forwarding reference.
The reason CallableObj co(RetXPrintU, 0, 8u);
worked in your example is because 0
is a prvalue and a prvalue can bind to an rvalue reference.
As the error message says, the compiler can't deduce the template parameters for CallableObj
. You need add a deduction guide for that:
template <typename Fn, typename... Args>
CallableObj(Fn&& fun, Args&&... args) -> CallableObj<Fn, Args>...>;
The whole code reads:
template <typename Fn, typename... Args>
class CallableObj
{
public:
explicit CallableObj(Fn fun, Args... args)
{
std::invoke(std::forward<Fn>(fun), std::forward<Args>(args)...);
}
};
template <typename Fn, typename... Args>
CallableObj(Fn&& fun, Args&&... args) -> CallableObj<Fn, Args...>;
As @Jarod42 pointed out there's no need to make the constructor a template itself (as it was in the first version of the answer).
And here is a live example (corrected version by @Jarod42).
I was assuming your code is just a minimal example and you need the class to be a template. If that is not the case you better go with the other solution.
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