Welcome to ShenZhenJia Knowledge Sharing Community for programmer and developer-Open, Learning and Share
menu search
person
Welcome To Ask or Share your Answers For Others

Categories

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?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
thumb_up_alt 0 like thumb_down_alt 0 dislike
457 views
Welcome To Ask or Share your Answers For Others

1 Answer

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.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
thumb_up_alt 0 like thumb_down_alt 0 dislike
Welcome to ShenZhenJia Knowledge Sharing Community for programmer and developer-Open, Learning and Share
...