Устранение неоднозначности указателя перегруженной функции-члена, передаваемого в качестве параметра шаблона

Я пытаюсь воссоздать шаблон наблюдателя, в котором я могу точно передать параметры заданной функции-члену наблюдателей.

Если я попытаюсь передать адрес функции-члена, которая имеет несколько переопределений, она не сможет определить правильную функцию-член на основе аргументов.

#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;
}

Это не может быть скомпилировано с template argument deduction/substitution failed.

Обратите внимание, что у меня были Args... и UArgs..., потому что мне нужно иметь возможность передавать параметры, которые не обязательно относятся к тому же типу, что и тип сигнатуры функции, но могут быть преобразованы в указанный тип.

Я думал, что могу использовать вызов std::enable_if<std::is_convertible<Args, UArgs>> для устранения неоднозначности, но не верю, что смогу сделать это с помощью пакета параметров вариативного шаблона?

Как мне заставить работать здесь вывод аргументов шаблона?


person Steve Lorimer    schedule 26.07.2013    source источник
comment
Неважно, как вы меняете call, проблема лежит на более высоком уровне. Кроме того, очень важно: не используйте std::forward внутри call для аргументов, так как таким образом они потенциально пересылаются несколько раз, и только один из наблюдателей получает неперемещенный аргумент для rvalue.   -  person Xeo    schedule 26.07.2013


Ответы (1)


Проблема здесь:

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

Для обеих строк компилятор не знает, о каком foo::func вы имеете в виду. Следовательно, вы должны устранить неоднозначность, предоставив информацию о типе, которая отсутствует (т.е. тип foo:func), с помощью приведения:

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

В качестве альтернативы вы можете предоставить аргументы шаблона, которые компилятор не может вывести и которые определяют тип func:

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

Обратите внимание, что вы должны использовать double, а не const double выше. Причина в том, что обычно double и const double - это два разных типа. Однако есть одна ситуация, когда double и const double рассматриваются, как если бы они были одного типа: как аргументы функции. Например,

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

не две разные перегрузки, а одна и та же функция.

person Cassio Neri    schedule 26.07.2013
comment
Я пробовал ваш подход static_cast к подписи std :: vector ‹int› :: iterator std :: vector ‹int› :: at (std :: vector ‹int› :: iterator, const std :: vector ‹int›: : value_type &), и это не работает. Я ожидал, что это связано с константностью, но во втором аргументе нет идентичной перегрузки без константности. Компилятор (msvc 13) жалуется, что не может преобразовать перегруженную функцию в неперегруженную подпись. - person user2813810; 09.02.2015
comment
@ user2813810 Есть только две перегрузки std::vector<int>::at: const_reference at(size_type n) const и reference at(size_type n) (см., например, здесь). Ни один из них не принимает и не возвращает итераторы. Так что я не уверен, что понимаю тебя. Почему бы вам не опубликовать новый вопрос (с коротким самодостаточным примером) и сделать перекрестную ссылку на этот пост, говоря, что это не решило вашу проблему? Может быть, я или кто-нибудь еще смогу помочь. - person Cassio Neri; 15.02.2015
comment
Да, проблема была в подписи. Я работал со всеми методами в контейнерах одновременно с помощью макроса, очень хотел спать и запутался. Оказывается, макрос был неправильным, объединял два детектора метода, а ошибка компилятора фактически заставляла его выглядеть так, как будто это подпись для std :: vector :: at. Еще один случай, когда макросы - зло. С тех пор изменилось так много, что я не мог даже воспроизвести это. Я бы вернулся, чтобы удалить, если бы не забыл об этом посте, когда заметил проблему. Вы знаете, как получить туннельное зрение, когда наконец замечаете ошибку? - person user2813810; 16.02.2015