web-dev-qa-db-fra.com

Supprimer l'ambiguïté du pointeur de fonction membre surchargé transmis en tant que paramètre de modèle

J'essaie de recréer le modèle d'observateur où je peux parfaitement transmettre des paramètres à une fonction membre donnée des observateurs.

Si j'essaye de passer l'adresse d'un fonction membre qui a remplacements multiples, il ne peut pas déduire la fonction membre correcte en fonction des 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;
}

Cela ne parvient pas à compiler avec template argument deduction/substitution failed.

Notez que j'avais Args... et UArgs... parce que je dois pouvoir passer des paramètres qui ne sont pas nécessairement du même type que le type de la signature de fonction, mais qui sont convertibles en ce type.

Je pensais pouvoir utiliser un std::enable_if<std::is_convertible<Args, UArgs>> appel à lever l'ambiguïté, mais je ne pense pas pouvoir le faire avec un pack de paramètres de modèle variadic?

Comment puis-je faire fonctionner la déduction d'argument de modèle ici?

41
Steve Lorimer

Le problème est ici:

l.call(&foo::func, "hello");
l.call(&foo::func, 0.5);

Pour les deux lignes, le compilateur ne sait pas quel foo::func vous parlez. Par conséquent, vous devez vous lever sans ambiguïté en fournissant les informations de type manquantes (c'est-à-dire le type de foo:func) via les transtypages:

l.call(static_cast<void (foo::*)(const std::string&)>(&foo::func), "hello");
l.call(static_cast<void (foo::*)(const double      )>(&foo::func), 0.5);

Alternativement, vous pouvez fournir les arguments de modèle que le compilateur ne peut pas déduire et qui définissent le type de func:

l.call<void, const std::string&>(&foo::func, "hello");
l.call<void, double            >(&foo::func, 0.5);

Notez que vous devez utiliser double et non const double au dessus. La raison en est que généralement double et const double sont de deux types différents. Cependant, il existe une situation où double et const double sont considérés comme s'ils étaient du même type: comme arguments de fonction. Par exemple,

void bar(const double);
void bar(double);

ne sont pas deux surcharges différentes mais sont en fait la même fonction.

44
Cassio Neri