C++11 中 enable_if 的三种用法

4,424 阅读2分钟

「这是我参与11月更文挑战的第 7 天,活动详情查看:2021最后一次更文挑战」。

参加该活动的第 13 篇文章

参考原文地址: std::enable_if 的几种用法

我对文章的格式和错别字进行了调整,并在他的基础上,根据我自己的理解把重点部分进一步解释完善(原作者的代码注释甚少)。以下是正文。

正文

由于最近在看模板元编程相关的文章,其中代码多多少少都会涉及到 enable_if ,它的应用如此之广泛、功能如此之强大,所以我也特地将所查的优秀资料进行总结归纳。回到 std::enable_if ,它的作用十分直观,即满足条件时该类型才有效(才有定义)

它的定义也相当的简单:

template <bool, typename T=void>
struct enable_if {
};

template <typename T>
struct enable_if<true, T> { ///< 第一个模板参数为 true
  using type = T;           ///< type 才有定义
};

由上可知,只有当第一个模板参数为 true 时,type 才有定义,否则使用 type 会产生编译错误,并且默认模板参数可以让你不必指定类型。

下面具体列举了它的三种使用方法

类型偏特化

在使用模板编程时,经常会用到根据模板参数的某些特性进行不同类型的选择,或者在编译时校验模板参数的某些特性。例如:

template <typename T, typename Enable=void>
struct check;

template <typename T>
struct check<T, typename std::enable_if<T::value>::type> { ///< T::value == true ?
  static constexpr bool value = T::value;
}; 

上述的 check 只希望选择 value==true 的 T,否则就报编译时错误。

如果想给用户更友好的提示,可以提供结构体的原型定义,并在其中进行 static_assert 的静态检查,给出更明确的字符串说明。

C++11 引入了 static_assert 关键字,用来实现编译期间的断言,叫静态断言。

语法:static_assert(常量表达式,错误提示字符串);

如果第一个参数常量表达式的值为 false ,会产生一条编译错误,错误位置就是该 static_assert 语句所在行,第二个参数就是错误提示字符串。 然后通过调用 abort 来终止程序运行。

控制函数返回类型

对于模板函数,有时希望根据不同的模板参数返回不同类型的值,进而给函数模板也赋予类型模板特化的性质。典型的例子可以参看 tuple 的获取第 k 个元素的 get 函数:


/// @note k==0
template <std::size_t k, class T, class... Ts>
typename std::enable_if<k==0, typename element_type_holder<0, T, Ts...>::type&>::type
get(tuple<T, Ts...> &t) {
  return t.tail; 
}

/// @note k!=0
template <std::size_t k, class T, class... Ts>
typename std::enable_if<k!=0, typename element_type_holder<k, T, Ts...>::type&>::type
get(tuple<T, Ts...> &t) {
  tuple<Ts...> &base = t;
  return get<k-1>(base); 
}

由于函数模板不能偏特化,通过 enable_if 便可以根据 k 值的不同情况选择调用哪个 get,进而实现函数模板的多态。

校验函数模板参数类型

有时定义的模板函数,只希望特定的类型可以调用,参考 cppreference 官网示例,很好的说明了如何限制只有整型可以调用的函数定义:


/// @note 返回值:利用 std::is_integral<T>::value
template <typename T>
typename std::enable_if<std::is_integral<T>::value, bool>::type
is_odd(T t) {
  return bool(t%2);
}

/// @note 默认模板参数
template <typename T, typename = typename std::enable_if<std::is_integral<T>::value>::type>
bool is_even(T t) {
  return !is_odd(t); 
}

一个通过返回值,一个通过默认模板参数,都可以实现校验模板参数是整型的功能。

std::is_integral::value —— 检查 T 是否是整型。提供成员常量值,如果 T 是 bool、char、char8_t(自 C++ 20)、char16_t、char32_t、wchar_t、short、int、long、long long任何实现定义的扩展整数类型,包括任何有符号、无符号和 cv 限定(const/volatile)的变量,则该成员常量值等于 true 。否则,value 等于 false