Template parameter is ambiguous: could not deduce template argument
Asked Answered
B

2

11

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?

Bonne answered 8/11, 2016 at 21:44 Comment(1)
This example is a re-creation of the problem, but not in any way the code I'm working on per-se.Bonne
L
14

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

Lynd answered 8/11, 2016 at 22:5 Comment(6)
Thanks for the idea, I actually managed to do it using std::decay.Bonne
@AlexandreBourlon decay is a bad choice here - if Value is a reference type you're now going to be passing a copy of the argument into cb instead, which isn't what you want.Lynd
Isn't it what also happen with your solution? That what I thought at first... Right now I'm using std::decay_t<ValueType> const&, so the type loose reference and cv-qualifier(s) if he has it, then I force a const& on the parameter. I know it's not a direct equivalent to the type passed at the call site, but it seems good enough for my use case here. I'm still open for better suggestion though.Bonne
@AlexandreBourlon No it isn't. Consider what happens if Value is an non-const lvalue reference. You're going to take your argument by const lvalue reference and try that pass that through.Lynd
So I looked a little more into non-deduced context and found this other SO question: #25245953. What do you think of std::common_type in this context?Bonne
@AlexandreBourlon common_type_t<T> is decay_t<T>, so it has the same problem.Lynd
T
4

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

Thayne answered 8/11, 2016 at 21:51 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.