ограничить шаблон элемента указателя функции только производными классами - PullRequest
1 голос
/ 15 марта 2019

Я пытаюсь ограничить вывод шаблона только для объектов из той же иерархии.

Код ниже компилирует

template<class T>
void RegisterAction(const string& actionId, bool(T::*f)())
{
    m_actions.emplace(actionId, std::bind(f, static_cast<T*>(this)));
}

, но этот код не

template<class T, typename std::enable_if_t<std::is_base_of<BaseClass, T>::value>>
void RegisterAction(const string& actionId, bool(T::*f)())
{
    m_actions.emplace(actionId, std::bind(f, static_cast<T*>(this)));
}

m_actions имеет тип std::unordered_map<string, std::function<bool()>>

Вот ошибка из Visual Studio

'BaseClass::RegisterAction': no matching overloaded function found
error C2783: 'void BaseClass::RegisterAction(const string &,bool (__cdecl T::* )(void))': could not deduce template argument for '__formal'

Вот как вы будете использовать метод:

void DerivedClass::InitActions()
{
    RegisterAction("general.copy", &DerivedClass::OnCopy);
    RegisterAction("general.paste", &DerivedClass::OnPaste);
}

Кстати, я не могу использовать static_assert, потому что там я использую c ++ 14.

У кого-нибудь есть идеи?

Ответы [ 2 ]

2 голосов
/ 15 марта 2019

Вы пытаетесь ввести новый параметр шаблона, чтобы вызвать ошибку подстановки - что правильно - но ваш синтаксис немного неверен. Что вы должны написать это:

template<class T, typename = std::enable_if_t<std::is_base_of<BaseClass, T>::value>>
                       // ^^^ this equal sign is crucial

Когда вы пишете typename = foo, вы объявляете параметр шаблона безымянного типа (это похоже на запись typename unused = foo) и устанавливаете значение по умолчанию для этого типа foo. Таким образом, если кто-то попытается создать экземпляр этого шаблона с T, не полученным из BaseClass, в аргументе по умолчанию произойдет ошибка замещения, что приведет к сбою вывода.

Поскольку вы написали его без знака равенства, typename std::enable_if_t<...> интерпретировался как спецификатор typename , то есть компилятор считает, что вы объявляете шаблон не тип параметр, тип которого typename std::enable_if_t<...>, который вы оставили безымянным. Следовательно, когда T получено из BaseClass, тип этого параметра шаблона равен void. Поскольку нетиповые параметры шаблона не могут иметь тип void (так как нет значений типа void), здесь возникает ошибка SFINAE.

Интересно, что и GCC, и Clang также не могут выдать полезное сообщение об ошибке . Они также жалуются на то, что неназванный аргумент шаблона не может быть выведен, вместо того, чтобы указывать на то, что void нетипичные параметры шаблона недопустимы (или даже просто указывают на то, что является нетипизированным параметром шаблона типа void).

0 голосов
/ 15 марта 2019

Когда предполагается использовать RegisterAction для регистрации только для тех классов, которые являются производными от BaseClass, кажется, что лучше явно указать static_assert, почему некоторые T нельзя использовать с RegisterAction, вместо того, чтобы просто «скрывать» проблему с SFINAE.

Итак

template<class T>
void RegisterAction(const string& actionId, bool(T::*f)())
{
    static_assert(
        std::is_base_of<BaseClass, T>::value,
        "T shall be derived from BaseClass for RegisterAction to work"
    );
    m_actions.emplace(actionId, std::bind(f, static_cast<T*>(this)));
}

будет громко и ясно кричать о точной причине того, почему RegisterAction не может принять некоторые действия.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...