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?
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.
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()
.
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).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With