Lao
Lao

Reputation: 248

Function argument template parameter ambiguous for const ref types

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

Answers (3)

NathanOliver
NathanOliver

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

Elohim Meth
Elohim Meth

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);
}

DEMO

Upvotes: 1

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

Related Questions