Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Disambiguate overloaded member function pointer being passed as template parameter

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?

like image 726
Steve Lorimer Avatar asked Jul 26 '13 06:07

Steve Lorimer


1 Answers

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.

like image 91
Cassio Neri Avatar answered Sep 27 '22 00:09

Cassio Neri