I am attempting to recreate the Observer pattern where I can perfectly forward parameters to a given member function of the observers.
If I attempt to pass the address of a member function which has multiple overrides, it cannot deduce the correct member function based on the arguments.
#include <iostream>
#include <vector>
#include <algorithm>
template<typename Class>
struct observer_list
{
template<typename Ret, typename... Args, typename... UArgs>
void call(Ret (Class::*func)(Args...), UArgs&&... args)
{
for (auto obj : _observers)
{
(obj->*func)(std::forward<UArgs>(args)...);
}
}
std::vector<Class*> _observers;
};
struct foo
{
void func(const std::string& s)
{
std::cout << this << ": " << s << std::endl;
}
void func(const double d)
{
std::cout << this << ": " << d << std::endl;
}
};
int main()
{
observer_list<foo> l;
foo f1, f2;
l._observers = { &f1, &f2 };
l.call(&foo::func, "hello");
l.call(&foo::func, 0.5);
return 0;
}
This fails to compile with template argument deduction/substitution failed
.
Note that I had Args...
and UArgs...
because I need to be able to pass parameters which are not necessarily the same type asthe type of the function signature, but are convertible to said type.
I was thinking I could use a std::enable_if<std::is_convertible<Args, UArgs>>
call to disambiguate, but I don't believe I can do this with a variadic template parameter pack?
How can I get the template argument deduction to work here?
The issue is here:
l.call(&foo::func, "hello");
l.call(&foo::func, 0.5);
For both lines, the compiler doesn't know which foo::func
you are referring to. Hence, you have to disambiguate yourself by providing the type information that is missing (i.e., the type of foo:func
) through casts:
l.call(static_cast<void (foo::*)(const std::string&)>(&foo::func), "hello");
l.call(static_cast<void (foo::*)(const double )>(&foo::func), 0.5);
Alternatively, you can provide the template arguments that the compiler cannot deduce and that define the type of func
:
l.call<void, const std::string&>(&foo::func, "hello");
l.call<void, double >(&foo::func, 0.5);
Notice that you have to use double
and not const double
above. The reason is that generally double
and const double
are two different types. However, there's one situation where double
and const double
are considered as if they were the same type: as function arguments. For instance,
void bar(const double);
void bar(double);
are not two different overloads but are actually the same function.