enable_if

1  导言


使用 enable_if 系列模板可以控制一个函数模板或类模板偏特化是否包含在基于模板参数属性的一系列匹配函数或偏特化中。比如,我们可以定义一个只对某些类型(通过特征类[traits class]定义)有效——当然也只匹配这些类型——的函数模板。 enable_if 也可以对类模板偏特化实现同样的效果。 enable_if 的应用在 [ 1] 和 [ 2] 中有详细的介绍。


1.1  大纲

namespace boost {
  template <class Cond, class T = void> struct enable_if;
  template <class Cond, class T = void> struct disable_if;
  template <class Cond, class T> struct lazy_enable_if;
  template <class Cond, class T> struct lazy_disable_if;

  template <bool B, class T = void> struct enable_if_c;
  template <bool B, class T = void> struct disable_if_c;
  template <bool B, class T> struct lazy_enable_if_c;
  template <bool B, class T> struct lazy_disable_if_c;
}

1.2  背景

C++ 中“类型敏感的”模板函数重载依赖于 SFINAE (substitution-failure-is-not-an-error) 原则 [ 3]:在函数模板的实例化过程中,如果形成的某个参数或返回值类型无效那么这个实例将从重载决议集中去掉而不是引发一个编译错误。下面这个例子,出自 [ 1],演示了这个原则的重要性。
int negate(int i) { return -i; }

template <class F>
typename F::result_type negate(const F& f) { return -f(); }

我们假设编译器遇到了 negate(1) 的调用。很明显第一个定义是个好选择,但是编译器必须在检查所有的定义后才能作出决定,这个检查过程包含对模板的实例化。使用 int 作为类型 F 对第二个定义进行实例化将产生:
int::result_type negate(const int&);

这里的返回值类型是无效的。 如果把这种情况看作是一种错误,那么添加一个无关的函数模板(从来不会被调用)也将导致原本有效的代码无法通过编译。由于 SFINAE 原则的存在,上面的例子不会产生编译错误,当然也不是良好的编程风格。在这种情况下编译器会简单地从重载决议集中抛弃后一个 negate 的定义。

enable_if 模板就是一个控制是否形成符合 SFINAE  条件的工具。

2  enable_if模板

enable_if 面板的名称分为三部分:一个可选的 lazy_ 标记、 enable_ifdisable_if和一个可选的 _c 标记。这三部分的 8 种组合都存在于该库中。 lazy_ 标记的作用在 3.3 中讨论。名称的第二部分表示使用值为 true 的参数启用当前重载还是禁用当前重载。名称的第三部分表示参数是一个 bool 值(有 _c 后缀)还是一个包含名称为 value 的静态 bool 值的类型。后者与 Boost.MPL 配合使用。

enable_if_cenable_if 的定义如下:(下面我们只写 enable_if,其实他们是被包含在 boost 命名空间中的。)
template <bool B, class T = void>
struct enable_if_c {
  typedef T type;
};

template <class T>
struct enable_if_c<false, T> {};

template <class Cond, class T = void>
struct enable_if : public enable_if_c<Cond::value, T> {};

如果参数 B 为 true, enable_if_c 模板的实例包含一个成员类型 type,被定义为类型 T。如果 B 为 false 则不会定义这样的成员类型。所以 enable_if_c<B, T>::type 可以是一个有效的或者无效的类型表达式,这取决于 B 的值。当有效时, enable_if_c<B, T>::type 等价于 T因此 enable_if_c 模板可以用来控制函数何时参与重载决议何时不参与。比如,下面这个函数对所有的算术类型(根据 Boost type_traits library 的分类)有效:

template <class T>
typename enable_if_c<boost::is_arithmetic<T>::value, T>::type 
foo(T t) { return t; }

disable_if_cenable_if_c 的功能一样,只是参数的含义相反。下面着函数对所有的“非算术类型”有效:
template <class T>
typename disable_if_c<boost::is_arithmetic<T>::value, T>::type 
bar(T t) { return t; }

为了和 Boost.MPL 一起使用,我们提供了接收任何包含名为 value 的 bool 型常量成员的类型作为参数的 enable_if 模板。MPL bool_、 and_or_、 和 not_ 都可以用来构建这种类型。Boost.Type_traits 库中的特征类也符合这个惯例。比如,上面例子中的 foo 函数也可以这样写:
template <class T>
typename enable_if<boost::is_arithmetic<T>, T>::type 
foo(T t) { return t; }

3  使用 enable_if

enable_if 模板在 boost/utility/enable_if.hpp 中定义,这个文件被 boost/utility.hpp 所包含

enable_if 既可以作为返回值也可以作为一个额外的参数。比如,上一节中的函数 foo 也可以这么些:
template <class T>
T foo(T t, typename enable_if<boost::is_arithmetic<T> >::type* dummy = 0); 

我们给这个函数添加了一个额外的 void* 类型的参数但是为它指定了一个默认值,所以这个参数对调用该函数的客户代码是不可见的。 注意 enable_if 的第二个模板参数没有给出,因为默认的 void 正好满足需要。

把控制条件作为一个参数或是返回值很大程度上取决于编程风格,但是对于某些函数来说只有一个可选项:
  • 运算符的参数个数是固定的,所以 enable_if 只能用作返回值。
  • 构造函数和析构函数没有返回值,所以只能添加一个额外的参数。
  • 对于类型转换运算符,好像还没有办法使用此机制。但是类型转换构造函数可以使用添加一个额外的参数的方法来使用此机制。

3.1 模板类偏特化的启用与禁用

类模板偏特化可以使用 enable_if 来控制其启用与禁用。为达到这个目的,需要为模板添加一个额外的模板参数用于控制启用与禁用。这个参数的默认值是 void。比如:
template <class T, class Enable = void> 
class A { ... };

template <class T>
class A<T, typename enable_if<is_integral<T> >::type> { ... };

template <class T>
class A<T, typename enable_if<is_float<T> >::type> { ... };

使用任何整数类型实例化的 A 与第一个偏特化匹配,任何浮点类型与二个相匹配。任何其他类型匹配主模板。条件可以是任何依赖模板参数的编译期逻辑型表达式。同样需要注意的是, enable_if 的第二个参数不需要给出;默认值( void)在这里可以胜任。

3.2  重叠的控制条件

一旦编译器在检查控制条件后把这个函数包含进重载决议集中,那么它就会进一步使用普通的 C++ 重载决议规则来选择最匹配的函数。具体来说,各个控制条件之间没有顺序之分,使用了控制条件的各个函数之间如果不能相互排斥,将导致二义性。比如:
template <class T>
typename enable_if<boost::is_integral<T>, void>::type 
foo(T t) {}

template <class T>
typename enable_if<boost::is_arithmetic<T>, void>::type 
foo(T t) {}

所有的整数类型也是算数类型。所以,对于 foo(1) 这样的调用,两个条件都是 true,于是两个都将包含进重载决议集中。它们都可以很好地被匹配,于是二义性就产生了。当然,如果通过其他的参数可以解决二义性,一个或多个控制条件同时为 true 是可以的。

以上讨论同样适用于在类模板片特化中使用 enable_if

3.3  惰性(lazy) enable_if

在某些情况下,在某个控制条件不为 true 时避免实例化一个函数签名的某个部分是很有用的。比如:
template <class T, class U> class mult_traits;

template <class T, class U>
typename enable_if<is_multipliable<T, U>, typename mult_traits<T, U>::type>::type
operator*(const T& t, const U& u) { ... }

假设类模板 mult_traits 是一个定义一个乘法运算符结果类型的特征类, is_multipliable 是一个定义可以用于该运算符的类型的特征类。只要对于某些类型 A 和 B, is_multipliable<A, B>::valuetrue 那么 mult_traits<A, B>::type 就会被定义出来。

现在,试着用类型 C 和 D 调用运算符 * (还有其他一些重载),其中 is_multipliable<C, D>::value falsemult_traits<C, D>::type 未定义。这种情况在某些编译器上是一种错误。这时,SFINAE 不起作用因为无效的类型是另外一个模板的参数。 在这种情况下可以使用 lazy_enable_iflazy_disable_if (还有它们的 _c 版本):
template<class T, class U>
typename lazy_enable_if<is_multipliable<T, U>, mult_traits<T, U> >::type
operator*(const T& t, const U& u) { ... }

lazy_enable_if 的第二个参数必须是一个在第一个参数(条件)为 true 时定义了一个名字为 type 的的内嵌类型。

注意
使用了一个特征类的一个成员类型或静态常量将导致这个特化的所有成员(类型和静态常量)被实例化。因此,如果你的特征类在某些情况下包含无效类型,那么你应该使用两个不同的模板来描述条件和类型映射(conditions and the type mappings)。在上面的例子中, is_multipliable<T, U>::value 定义了何时 when mult_traits<T, U>::type 有效。

3.4  编译器变通解决方案

如果在控制者(enabler)中唯一用于解决二义性的因子是另外一个条件,那么某些编译器标志会导致二义性。比如,某些编译器(例如 GCC 3.2)将会把下面两个函数诊断为具有二义性:
template <class T>
typename enable_if<boost::is_arithmetic<T>, T>::type 
foo(T t);

template <class T>
typename disable_if<boost::is_arithmetic<T>, T>::type 
foo(T t);

有两个临时解决方案:
  • 使用一个额外的参数用于解决二义性。使用默认值使它对调用者不可见。比如:
    template <int> struct dummy { dummy(int) {} };
    
    template <class T>
    typename enable_if<boost::is_arithmetic<T>, T>::type 
    foo(T t, dummy<0> = 0);
    
    template <class T>
    typename disable_if<boost::is_arithmetic<T>, T>::type 
    foo(T t, dummy<1> = 0);
    


  • 在不同的命名空间中定义这两个函数,然后使用 using 把它们带到同一个命名空间中:
    namespace A {
      template <class T>
      typename enable_if<boost::is_arithmetic<T>, T>::type 
      foo(T t);
    }
    
    namespace B {
      template <class T>
      typename disable_if<boost::is_arithmetic<T>, T>::type 
      foo(T t);
    }
    
    using A::foo;
    using B::foo;
    
    
    注意,第二个解决方案不能用在成员模板中。另一方面,运算符不能接受额外的参数,所以第一个解决方案不可使用。总结一下:这两个解决方案都不能用于需要定义为成员方法的模板化的运算符(赋值和下标运算符)。

4  Acknowledgements

We are grateful to Howard Hinnant, Jason Shirk, Paul Mensonides, and Richard Smith whose findings have influenced the library.

References

[1]
Jaakko Järvi, Jeremiah Willcock, Howard Hinnant, and Andrew Lumsdaine. Function overloading based on arbitrary properties of types. C/C++ Users Journal, 21(6):25--32, June 2003.

[2]
Jaakko Järvi, Jeremiah Willcock, and Andrew Lumsdaine. Concept-controlled polymorphism. In Frank Pfennig and Yannis Smaragdakis, editors, Generative Programming and Component Engineering, volume 2830 of LNCS, pages 228--244. Springer Verlag, September 2003.

[3]
David Vandevoorde and Nicolai M. Josuttis. C++ Templates: The Complete Guide. Addison-Wesley, 2002.
<think>好的,用户想了解enable_if和类型萃取。首先,我需要确认这两个概念在C++中的位置,都属于模板元编程的范畴。 enable_if,我记得是用来在模板参数满足某些条件时启用或禁用特定的模板重载。它的实现依赖于SFINAE(替换失败并非错误)原则。当模板参数不符合条件时,该重载会被忽略,编译器会选择其他合适的版本。 类型萃取(Type Traits)则是用来在编译时获取类型的信息或对类型进行转换的工具。比如判断一个类型是否为整数、是否为指针等。标准库中的<type_traits>头文件提供了很多这样的模板类。 接下来需要解释这两个概念如何结合使用。例如,enable_if常常依赖于类型萃取的结果作为条件,来选择性启用函数模板或类模板。 可能需要举例说明。比如,编写一个函数模板,只允许整数类型参数,这时可以用enable_if配合is_integral类型萃取。当条件满足时,函数才会被实例化,否则编译器忽略,避免错误。 还要注意两者的语法细节。enable_if通常作为模板参数的默认参数,或者作为函数返回类型。类型萃取则通过成员value或::value来获取布尔结果。 用户可能想知道实际应用场景。比如在重载函数时避免歧义,或者优化特定类型的处理。比如标准库中的advance函数,针对不同的迭代器类别(随机访问、双向等)有不同的实现,通过类型萃取判断迭代器类型,并用enable_if选择对应的实现。 需要确保解释清晰,分步骤说明。先介绍类型萃取,再讲enable_if,再结合例子展示如何一起使用。同时要提醒常见的错误,比如条件书写错误导致的编译失败,或者SFINAE的正确应用方式。 最后总结两者的作用和重要性,强调它们在模板元编程中对代码灵活性和安全性的提升。</think>$\textsf{enable\_if}$ 和 $\textsf{类型萃取}$ 是 C++ 模板元编程中的两个重要工具,用于实现 $\textsf{编译期条件判断}$ 和 $\textsf{类型特性检查}$。以下是逐步解释: --- ### 一、类型萃取 (Type Traits) $\textsf{类型萃取}$ 用于在编译期获取类型特征,标准库在 $\texttt{<type\_traits>}$ 中提供预定义工具: #### 1. 基础示例 ```cpp // 判断类型是否为整数 static_assert(std::is_integral<int>::value, "int是整型"); // 通过 static_assert(!std::is_floating_point_v<int>, "非浮点型"); // C++17简化写法 ``` #### 2. 工作原理 通过模板特化实现: ```cpp template<typename T> struct is_pointer { static constexpr bool value = false; }; template<typename T> struct is_pointer<T*> { // 针对指针类型的特化版本 static constexpr bool value = true; }; ``` --- ### 二、enable\_if 的作用 $\textsf{enable\_if}$ 用于根据条件 $\textsf{选择性启用}$ 函数模板或类模板的重载,基于 $\textsf{SFINAE}$ 原则(替换失败不是错误)。 #### 1. 标准库定义 ```cpp template<bool B, typename T = void> struct enable_if {}; template<typename T> struct enable_if<true, T> { // 当条件为true时定义type成员 using type = T; }; ``` #### 2. 典型用法 ```cpp // 仅当T是整型时启用该函数 template<typename T> typename std::enable_if<std::is_integral<T>::value, void>::type process(T num) { // 处理整型 } ``` --- ### 三、组合应用案例 #### 1. 函数模板重载控制 ```cpp // 版本1:处理整型 template<typename T> std::enable_if_t<std::is_integral_v<T>, void> handle(T val) { std::cout << "整型处理" << std::endl; } // 版本2:处理浮点型 template<typename T> std::enable_if_t<std::is_floating_point_v<T>, void> handle(T val) { std::cout << "浮点型处理" << std::endl; } ``` #### 2. 类模板特化控制 ```cpp template<typename T, typename = void> class MyContainer; // 基础模板不实现 // 仅当T可拷贝时启用特化版本 template<typename T> class MyContainer<T, std::enable_if_t<std::is_copy_constructible_v<T>>> { // 实现代码... }; ``` --- ### 四、关键差异总结 | 特性 | 类型萃取 | enable\_if | |---------------------|----------------------------------|--------------------------------| | **目的** | 检查/修改类型特征 | 根据条件启用/禁用代码 | | **输出形式** | 通过::value或::type提供结果 | 通过::type存在性控制代码生成 | | **典型应用场景** | 类型分类、特征判断 | 函数重载选择、模板特化控制 | --- ### 五、实际应用场景 1. **算法优化**:对迭代器类型进行判断(如$\texttt{std::advance}$) 2. **接口约束**:限制模板参数类型(如只接受算术类型) 3. **元编程**:编译期生成不同类型代码 通过 $\textsf{enable\_if + 类型萃取}$ 的组合,可实现 $\textsf{零运行时开销}$ 的类型安全控制,这是 C++ 模板元编程的核心技术之一。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值