C++中 enable_if 的用法( 以Sophus库为例 )

1,452 阅读2分钟

作者: 边城量子 ( shihezichen@live.cn )

背景

在阅读 Sophus 库代码时, 经常会遇到 std::enable_if 的使用, 例如在 common.h 中就有如下的代码

common.h:

template <bool B, class T = void>
using enable_if_t = typename std::enable_if<B, T>::type;

这里的 enable_if 的作用是什么? 它可以用在什么地方呢?

说明

  • 含义: enable_if 带两个模板元参数, 第一个是 bool , 第二个为 typename, 它的意思就是当第一个参数为 true 时, 第二个参数有效, 否则没有定义. 主要用在模板元变成场景中.
  • 定义 在标准库中, enable_if 是通过结构体模板来实现的, 声明如下
    template<bool B, typename T=void>
    struct enable_if {};
    
    template<typename T>
    struct enable_if<true, T> {
        typedef T type;
    }
    
    只有当第一个模板参数 B 为true时, enable_if 会包含一个 type=T 的成员, 否则就没有该成员;

Sophus 库中的实际应用举例讲解(1)

so2.hpp :

   /// Returns closed SO2 given arbitrary 2x2 matrix.
   ///
   template <class S = Scalar>
   static SOPHUS_FUNC enable_if_t<std::is_floating_point<S>::value, SO2>
   fitToSO2(Transformation const& R) {
     return SO2(makeRotationMatrix(R));
   }
  • 以上代码定义了一个函数 fitToSO2, 入参是一个 2x2 矩阵(参见Transformation的定义):

    using Transformation = Matrix<Scalar, N, N>;

  • 矩阵的数据类型为 S = Scalar, 使用的是模板参数

  • 函数的返回类型定义时, 使用了 enable_if_t 上面代码enable_if_t<std::is_floating_point<S>::value, SO2> 的意思就是:

    • 如果数据类型 S 是浮点数时, 则函数的返回值为 SO2;
    • 否则函数返回值没有定义. 其中 enable_if_t 的定义见本文最初部分, 如下:
    template <bool B, class T = void>
    using enable_if_t = typename std::enable_if<B, T>::type;
    
  • 为什么要用 enable_if? 如果我们是希望只有当入参为浮点数类型时, fitToSO2 才有返回的数据类型 SO2, 当传入其他类型数据时, fitToSO2 不返回 SO2, 而是返回其他类型数据, 或者如 Sophus 中这么定义的, 直接让编译器报错. 此时, 就可以使用 enable_if 根据入参的类型不同, 来控制函数的返回值类型, 达到所需的效果.

Sophus 库中的实际应用举例讲解(2)

test_macros.hpp :

 template <class Ptr>
 class Pretty<Ptr, enable_if_t<std::is_pointer<Ptr>::value>> {
  public:
   static std::string impl(Ptr ptr) {
     std::stringstream sstr;
     sstr << std::intptr_t(ptr);
     return sstr.str();
   }
 };
  • 以上代码定义了一个calss , 名为 Pretty, 类的作用主要是对指针Ptr进行美化的文本格式化.

  • 类有两个模板参数, 第一个是Ptr, 第二个使用 enable_if_t 定义, 语句 enable_if_t<std::is_pointer<Ptr>::value>> 的含义是, 如果Ptr是指针, 则第二个模板参数为void, 否则第二个模板参数没有定义, 编译器出错.

其他用途

此外, 在其他场合, 例如一个函数模板的有多个模板参数,且需要对其中一个模板参数进行特化, 其他模板参数不进行特化时, 怎么处理? 由于函数模板不能支持偏特化, 即模板参数只有全特化, 此时就可以使用 enable_if 对其中一个模板参数进行判断,针对每种条件都写一个函数实现体, 间接的实现函数特化.

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

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