std::enable_if的实现原理
主要使用了SFINAE
(Substitution Is Not A Error)原理。
推断失败并不是错误,应当通过控制编译器来选择正确的方法。
关于SFINE
这个就是SFINAE
的思想。通过C++来体现SFINAE
的编程技巧如例1所示。
#include<iostream>
#include<type_traits>
template<bool, typename T=void>
struct test_SFINAE {
};
template<typename T> //partial specialization
struct test_SFINAE<true,T> { //#5
typedef int type;
};
template<typename T, typename test_SFINAE<std::is_integral<T>::value, bool>::type = 0>
void test_SFINAE_func() // #3.Only T is int can implement successfully.
{
std::cout << "It is int" << std::endl;
}
template<typename T, typename test_SFINAE<std::is_floating_point<T>::value,bool>::type = 0>
void test_SFINAE_func() //#4.Only T is float can implement successfully.
{
std::cout << "It is float" << std::endl;
}
void _test_all()
{
test_SFINAE_func<int>(); //#1 "It is int"
test_SFINAE_func<float>(); //#2 "It is float"
}
例1中可以看到,#1被具体化成int,因为#4中,只有float才可以编译成功,所以编译器选择了#3的模板函数。编译器的判断顺序如下图1所示。
以上就是演示控制编译器进行SFINAE思想的过程。总的来讲就是
告诉编译器按照这个类型进行具体化是错误的 --> 同时提供一个可以将这个类型进行正确具体化的模板
enable_if的实现
大部分编译器的实现方法如下
template<bool, typename T=void>
struct enable_if{};
template<typename T>
struct enable_if<true, T>{}
T type;
;
可以看到我的例1就是仿照enable_if的实现>_<。
tips:
关于is_integral, is_floating_point的使用方法和原理可以网上搜索了解。
使用enable_if的场景
上面了解到了enable_if实现的基本原理,那么现在介绍一下用到这个功能的
- 作为函数的返回值(控制函数的返回类型)
- 作为函数的入参(控制入参类型的值,不怎么使用到)
- 作为模板的模板参数(控制编译器使用正确的模板)
enable_if经常与is_xxx(例如上面例子的is_integral)进行搭配,用来进行各种的类型判断和检查是否存在成员的场景用到。
使用std::enable_if的陷阱
- 注意使用is_xxx的时候,如果是同时使用is_xxx的特性,模板中需要使用非类型参数来替代类型参数,这里使用cppreference的例子来展示。
/* WRONG */
struct T {
enum { int_t, float_t } type;
template <typename Integer,
typename = std::enable_if_t<std::is_integral<Integer>::value>
>
T(Integer) : type(int_t) {}
template <typename Floating,
typename = std::enable_if_t<std::is_floating_point<Floating>::value>
>
T(Floating) : type(float_t) {} // error: treated as redefinition
};
/* RIGHT */
struct T {
enum { int_t, float_t } type;
template <typename Integer,
std::enable_if_t<std::is_integral<Integer>::value, bool> = true //#1
>
T(Integer) : type(int_t) {}
template <typename Floating,
std::enable_if_t<std::is_floating_point<Floating>::value, bool> = true //#2
>
T(Floating) : type(float_t) {} // OK
};
注意#1中=ture
,因为这个在#1中代表了模板非类型参数的默认值,所以在#1中可以false
来修改成false的默认值也是可以的,因为代码中并没有使用到这个值。同时也可以修改bool
的类型,改成char或者int等也可以,只是不同类型占用的字节数可能会更大。