Reputation: 1181
I want to build a structure that allow me to call member functions with an undefined number of parameters. For now I wrote something like this
template<typename Class, typename Return, typename ... Args>
struct Caller
{
private:
std::function<Return(Args ...)> callerFunction;
Caller() = delete;
Caller(const Caller&) = delete;
Caller(Caller&&) = delete;
Caller& operator=(const Caller&) = delete;
public:
~Caller() = default;
Caller(Class& instance, Return(Class::*function)(Args ...))
{
callerFunction = [&instance, function](Args... args)
{
return (instance.*function)(args ...);
};
}
Return operator() (Args ... args)
{
return callerFunction(args ...);
}
};
I am afraid that I cannot work around the fact that I cannot declare a std::function variable as std::function<Return<Args&& ...)> callerFunction
When I try to do this the compiler says that it cannot convert from int
to int&&
(if for example the parameters are int
s), so I'm guessing that the function sees the Args&& ...
as a parameter pack of rvalue references. Am I correct?
Is there a workaround?
Edit: Ok, I was declaring the function inside the Caller constructor in the wrong way.
Wrong way --> Caller(Class& instance, Return(Class::*function)(Args&& ...))
Right way --> Caller(Class& instance, Return(Class::*function)(Args ...))
The (I guess) right implementation is
template<typename Class, typename Return, typename ... Args>
struct Caller
{
private:
std::function<Return(Args&& ...)> callerFunction;
Caller() = delete;
Caller(const Caller&) = delete;
Caller(Caller&&) = delete;
Caller& operator=(const Caller&) = delete;
public:
~Caller() = default;
Caller(Class& instance, Return(Class::*function)(Args ...))
{
callerFunction = [&instance, function] (Args&&... args)
{
return (instance.*function)(std::forward<Args>(args) ...);
};
}
Return operator() (Args&& ... args)
{
return callerFunction(std::forward<Args>(args) ...);
}
};
Now the question is: Why do I need to declare the function anyway without the double &?
Upvotes: 4
Views: 2082
Reputation: 740
When you define your class template like this:
template <typename T>
struct A {
A(T&& param)
}
And then create an instace:
A<int> myInstance(someIntVariable);
It won't compile. The reason is, the type of T
is explicitly specified by you (as an int
, in A<int>
), and your class constructor parameter is no longer T&&
, but int&&
, so it's no longer universal reference (which accepts both lvalue and rvalue references), but regular rvalue reference.
Next, if you pass it some integer, there is a type missmatch error because you pass a regular variable when rvalue reference is expected.
In your example you explicitly defined function signatures, so the same applies - constructor expects a function taking rvalue references to Args..., but that's not true.
I think it's better explained in this question
Upvotes: 1
Reputation: 275810
int&&
is not the same as int
. If your method takes an int
, it doesn't take an int&&
. They are different types.
There is no conversion between different signatures of member function pointer.
Forwarding references aren't magic. They depend on deduction rules (and no use of Args&&...
above is in a deduced context) and on reference collapsing rules.
As such, your
Return operator() (Args&& ... args)
is also wrong. If Args...
is int
, then you won't be able to call the above with int a; blah.operator()(a)
as a
won't bind to int&&
.
Honestly, your entire type should be thrown out and replaced with a std::function<Return(Args...)>
. Class
is a useless narrowing of what the function does, and your wrappers don't add much either.
Users can just [&a](auto&&...args)->decltype(auto){ return a.foo(decltype(args)(args)...); }
if they really need to replicate your constructor, or use std::bind( &A::foo, std::ref(a) )
Upvotes: 0