将函数指针成员模板限制为仅派生类

时间:2019-03-15 16:23:43

标签: c++ function c++11 templates member

我试图将模板推论仅限于来自相同层次结构的对象。

下面的代码编译

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 个答案:

答案 0 :(得分:2)

您正在尝试引入新的模板参数以引起替换错误-这是正确的-但您的语法略有错误。您应该写的是:

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-specifier ,也就是说,编译器认为您正在声明 non-type < / em>模板参数,其类型为typename std::enable_if_t<...>,但未命名。因此,当从T导出BaseClass时,此模板参数的类型为void。由于非类型模板参数不能具有类型void(因为没有类型void的值),因此此处发生SFINAE错误。

有趣的是,GCC和Clang fail to give a useful error message。他们还抱怨无法推导未命名模板参数,而不是指出void非类型模板参数无效(甚至只是指出它是非类型模板)类型void的参数。

答案 1 :(得分:0)

当打算使用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无法接受某些动作的确切原因。