作者: 边城量子 ( 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);
}