给定以下情况:
class A {};
class B : public A {};
class C : public A {};
class D : public A {};
template<typename T/*std::enable_if and std::is_base_of here*/> class X {};
当我声明一个 X 时
然后我想约束typename T
强制成为 A 的子类,否则我会得到编译错误。
int main()
{
X<B> x1 = {}; //should work;
X<C> x2 = {}; //should work;
X<D> x2 = {}; //should work;
X<std::string> = {}; //should generate a compiling error;
X<int> = {}; //should generate a compiling error;
};
使用enable_if
的正确语法是
template<typename T, std::enable_if_t<std::is_base_of_v<A, T>, bool> = true>
class X {};
现在,如果A
是T
的基础,那么std::is_base_of_v<A, T>
true
,std::enable_if_t
变得bool
,我们赋予它true
的价值。 如果A
不是T
的基础,则条件为假,std::enable_if_t
结果为无,模板将作为可行的候选对象被丢弃,并将生成编译器错误。
或者,static_assert
可能就足够了:
template<typename T>
class X {
static_assert(std::is_base_of<A, T>::value, "!");
};
第三个也是同样常见的变体
static_assert
来自Jarod42:s的答案,以及- 使用
std::enable_if_t
作为非类型模板参数的类型,默认为 NathanOliver 答案中的某个值(template<..., std::enable_if_t<..., bool> = true>
)
是使用std::enable_if_t
作为类型模板参数的默认模板参数(与非类型模板参数的类型相比):
template<typename T,
typename = std::enable_if_t<std::is_base_of_v<A, T>>>
class X {};
这比使用非类型模板参数方法要简短一些。
然而,它有一个缺点,当用于函数模板时,它不能与仅基于谓词的相反结果的 SFINAE 重载两个变体组合:
// Not OK.
template<typename T,
typename = std::enable_if_t<std::is_base_of_v<A, T>>>
void foo(const T&) { /* some impl */ }
// Error: re-definition of foo
template<typename T,
typename = std::enable_if_t<!std::is_base_of_v<A, T>>>
void foo(const T&) { /* another impl */ }
由于这两个重载仅在其默认模板参数上有所不同,该参数不是函数模板签名的一部分,因此它们声明了具有相同签名的两个不同的函数模板,这是非法的。
如果我们使用非类型模板方法代替,这不是问题:
// OK
template<typename T,
std::enable_if_t<std::is_base_of_v<A, T>>* = nullptr>
void foo(const T&) { /* some impl */ }
template<typename T,
std::enable_if_t<!std::is_base_of_v<A, T>>* = nullptr>
void foo(const T&) { /* another impl */ }