我试图将模板推论仅限于来自相同层次结构的对象。
下面的代码编译
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。
没人有什么主意吗?
答案 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无法接受某些动作的确切原因。