c++templatesambiguoustemplate-argument-deductionambiguous-call

Template parameter is ambiguous: could not deduce template argument


I'm doing some kind of wrapper that looks like this:

#include <iostream>

template<class T, class Value>
void Apply(void (T::*cb)(Value), T* obj, Value v)
{
    (obj->*cb)(v);
}

class Foo
{
public:
    void MyFunc(const int& i)
    {
        std::cout << i << std::endl;
    }

    const int& GetValue()
    {
        return i_;
    }

private:
    int i_ = 14;
};

int main()
{
    Foo f;
    Apply(&Foo::MyFunc, &f, f.GetValue());
}

And I'm getting this error:

So I get it that it comes from template parameter deduction, however I fail to understand how. Why would Value not evaluate to const int& both times?


Solution

  • Why it fails

    Currently, the template parameter Value is deduced in two different places in the call to Apply: from the pointer to member function argument and from the last argument. From &Foo::MyFunc, Value is deduced as int const&. From f.GetValue(), Value is deduced as int. This is because reference and top-level cv-qualifiers are dropped for template deduction. Since these two deductions for the parameter Value differ, deduction fails - which removes Apply() from the overload set, and as a result we have no viable overload.

    How to fix it

    The problem is that Value is deduced in two different places, so let's just prevent that from happening. One way is to wrap one of the uses in a non-deduced context:

    template <class T> struct non_deduced { using type = T; };
    template <class T> using non_deduced_t = typename non_deduced<T>::type;
    
    template<class T, class Value>
    void Apply(void (T::*cb)(Value), T* obj, non_deduced_t<Value> v)
    {
        (obj->*cb)(v);
    }
    

    The last argument, v, is of type non_deduced_t<Value> which, as the name suggests, is a non-deduced context. So during the template deduction process, Value is deduced as int const& from the pointer to member function (as before) and now we simply plug that into the type for v.

    Alternatively, you could choose to deduce cb as its own template parameter. At which point Apply() just reduces to std::invoke().