使用 std::enable::if 和 std::is_base_of 来约束固有



给定以下情况:

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 {};

现在,如果AT的基础,那么std::is_base_of_v<A, T>truestd::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 */ }

最新更新