Reputation: 248
I have a problem passing const ref parameters to template functions calling other functions. Consider the following code:
struct A
{
void foo(const int& i) { }
};
template <class ...Args>
void a_caller(A& a, void(A::*f)(Args...), Args&& ...args)
{
(a.*f)(std::forward<Args>(args)...);
}
int main()
{
int i = 42;
A a;
a_caller(a, &A::foo, i); // (1) compiler error
a_caller<const int&>(a, &A::foo, i); // (2) ok
}
So, I have a member function A::foo
with the const int&
argument which I want to call in the wrapper a_caller
. The line (1) causes the following error:
'void a_caller(A &,void (__thiscall A::* )(Args...),Args &&...)' : template parameter 'Args' is ambiguous
see declaration of 'a_caller'
could be 'const int&'
or 'int&'
My first question is why this happens? I give the compiler a non-overloaded function A::foo, why can not it deduce Args
from it?
The second question is why this does not happen for std::make_unique then? The following code looks same to me, but the compiler has no problem deducing the constructor argument type:
struct A
{
A(const int& i) { }
};
int main()
{
int i = 42;
auto aptr = std::make_unique<A>(i);
}
Upvotes: 3
Views: 1600
Reputation: 180630
The error message tells you what is going on
see declaration of 'a_caller'
could be 'const int&'
or 'int&'
So you are passing member function that takes a const int&
so the compiler deduces that Args
as a const int&
but you also pass i
for Args
which it deduces as an int&
. These conflict so you get an error. You could const_cast
i
and that will compile or you could pass a const int
as the second parameter
a_caller(a, &A::foo, const_cast<const int&>(i));
const int foo = 42;
a_caller(a, &A::foo, foo);
Upvotes: 1
Reputation: 1827
My first question is why this happens? I give the compiler a non-overloaded function A::foo, why can not it deduce Args from it?
Because you try to deduce Args twice, for first and second parameters of function a_caller. And this deduced types do not match, const int&
for first parameter and int&
for second parameter.
The second question is why this does not happen for std::make_unique then?
Because make_unique just forwards its arguments to class constructor.
I think your code should look like this:
#include <memory>
struct A
{
void foo(const int& i) { }
};
template <typename F, class ...Args>
void a_caller(A& a, F &&f, Args&& ...args)
{
(a.*f)(std::forward<Args>(args)...);
}
int main()
{
int i = 42;
A a;
a_caller(a, &A::foo, i);
}
Upvotes: 1
Reputation: 171127
You're trying to shoehorn Args
into fulfilling two distinct (and not necessarily compatible) roles. The first role is the type of the parameters of f
. The second is the type of arguments given to a_caller
.
Because of the way perfect forwarding is implemented, passing i
as in your example want to deduce the Args
type for this i
as int &
. However, the same Args
type in A::foo
is of type const int &
—hence the ambiguous deduction.
In a way, the whole point of perfect forwarding is that the type of the forwarded argument is deduced on the spot (and is usually not reusable for anything else). So you'd have to do something like this:
template <class ...Params, class ...Args>
void a_caller(A& a, void(A::*f)(Params...), Args&& ...args)
{
(a.*f)(std::forward<Args>(args)...);
}
You'll have to rely on the invocation of f
telling you when the arguments don't match the parameters.
Upvotes: 7