Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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:

  • Apply: no matching overloaded function found.
  • void Apply(void (__thiscall T::* )(Value),T *,Value): template parameter Value is ambiguous, could be int or const int &.
  • void Apply(void (__thiscall T::* )(Value),T *,Value): could not deduce template argument for Value from const int.

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?

like image 826
HiroshimaCC Avatar asked Nov 08 '16 21:11

HiroshimaCC


2 Answers

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().

like image 105
Barry Avatar answered Oct 13 '22 01:10

Barry


The expression f.GetValue() is an lvalue of type const int. When this is passed by value, template argument deduction deduces the type int. In general, deducing Value from Value v will never produce a reference or a type with top-level cv-qualification.

You'll probably want to have two separate template parameters in place of Value (one for the function type's argument, one for the actual argument type) and use SFINAE to disable Apply when cb isn't callable with v (or static_assert for a hard error).

like image 32
Brian Bi Avatar answered Oct 13 '22 00:10

Brian Bi