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

restrict function pointer member template to only derived classes

我试图将模板推导限制为仅来自同一层次结构的对象。

下面的代码可以编译

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。

没有人知道吗?

您试图引入一个新的模板参数以引起替换错误---这是正确的---但您的语法有点不正确。你应该写的是:

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 std::enable_if_t<...> 的模板参数,您未命名。因此,当 T 派生自 BaseClass 时,此模板参数的类型为 void。由于非类型模板参数不能具有类型 void(因为没有类型 void 的值),因此此处会出现 SFINAE 错误。

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

类型的模板参数

当打算使用 RegisterAction 仅注册那些派生自 BaseClass 的 类 时,似乎最好用 static_assert 明确说明为什么某些 T 不能与RegisterAction 而不仅仅是 "hiding" 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 无法接受某些操作的确切原因。