enable_if

来源:互联网 发布:淘宝卖家订单怎么打印 编辑:程序博客网 时间:2024/05/17 02:59

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  条件的工具。

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 模板。MPLbool_、 and_or_、 和 not_ 都可以用来构建这种类型。Boost.Type_traits 库中的特征类也符合这个惯例。比如,上面例子中的 foo 函数也可以这样写:
template <class T>typename enable_if<boost::is_arithmetic<T>, T>::type foo(T t) { return t; }

使用 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>::typeoperator*(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>::valuefalsemult_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> >::typeoperator*(const T& t, const U& u) { ... }
lazy_enable_if 的第二个参数必须是一个在第一个参数(条件)为 true 时定义了一个名字为 type 的的内嵌类型。

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