C++ enable_if的使用

本文深入探讨C++中enable_if特性的应用,讲解如何根据不同类型的条件实例化模板,包括函数重载的局限性、SFINAE原则以及enable_if的具体使用场景。

http://www.fuzihao.org/blog/2016/07/14/C-enable-if%E7%9A%84%E4%BD%BF%E7%94%A8/

C++的enable_if常用于构建需要根据不同的类型的条件实例化不同模板的时候。本文主要讲了enable_if的使用场景和使用方式。 ## 函数重载的缺陷 函数重载能解决同名函数针对不同传入参数类型而实现不同的功能。举一个简单的例子:

1
2
3
4
5
6
7
8
9
10
void print(int a){
    cout<<"in int print";
}
void print(double a){
    cout<<"in double print";
}
int main(){
    f(1);
    f(1.0);
}

 

输出:

in int f()
in double f()

可以看出,这里的选择方式是通过不同的参数类型实现的。那么问题来了,如果我们是写的模板,想根据模板的条件来选择实现该怎么办?(例如,对于我们定义的一些class做输入时,采用一种方式实现,而对于其他类型的话采用另一种方式)。这就需要用到enable_if

SFINAE原则与enable_if简介

C++模板函数重载依赖于 SFINAE (substitution-failure-is-not-an-error) 原则,即替换失败不认为是错误,而只是简单地pass掉。看下面一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <iostream>
using namespace std;
void f(double a){
    cout<<"in double f()"<<endl;
}
template<typename T>
void f(typename T::noexist a){
    cout<<"in T::noexist f()"<<endl;
}
int main(){
    f(1);
    f(1.0);
}

 

程序正常编译通过,输出:

in double f()
in double f()

可以看到double和int都没有一个叫noexist的类型,所以解析是失败的,但是直接跳过,调用f的时候都转换为double输出。利用这个原则,我们可以构建一个开关的类,当满足某一条件时,让某类型能出现,不满足时,让他没有该类型,解析失败。这个开关函数就是 enable_if。enable_if是c++的标准模板,其实现非常简单,这里我们给出其实现的一种方式:

1
2
3
4
template<bool B, class T = void>
    struct user_enable_if {};
template<class T>
struct user_enable_if<true, T> { typedef T type; };

 

这里我们部分偏特化了当条件B为true时的模板user_enable_if,与普通的user_enable_if的区别就在于定义了type类型,这样,用户使用typename user_enable_if<cond, Type>::type时,当cond为true时,这个表达式是一个类型,而当cond为false时,该表达式解析失败。

enable_if的使用场景

enable_if可以作为参数或返回值加到函数中,我们看具体的例子: 1. 作为参数传入 我们在函数参数里多加了一个参数作推导用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include <iostream>
using namespace std;
template<bool B, class T = void>
    struct user_enable_if {};
template<class T>
struct user_enable_if<true, T> { typedef T type; };

struct A{};

template<typename T>
struct Traits{
    static const bool is_basic = true;
};

template<>
struct Traits<A>{
    static const bool is_basic = false;
};

template<typename T>
void f(T a, typename user_enable_if<Traits<T>::is_basic, void>::type* dump= 0){
    cout<<"a basic type"<<endl;
}

template<typename T>
void f(T a, typename user_enable_if<!Traits<T>::is_basic, void>::type* dump= 0){
    cout<<"a class type"<<endl;
}

int main(){
    A a;
    f(1);
    f(a);
}

 

运行输出:

a basic type
a class type

在这里,当f的输入是1时,Traits::is_basic为true,user_enable_if<Traits::is_basic>::type能得到一个type(void),因此能实例化,而第二个模板不能实例化。而当f的输入是a时,结果正好相反。但有时后我们对参数个数有限制(例如,我们是重载的operator函数,参数个数被严格限制),这时候我们可以把enable_if加到返回值上。 1. 作为返回值 代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include <iostream>
using namespace std;
template<bool B, class T = void>
    struct user_enable_if {};
template<class T>
struct user_enable_if<true, T> { typedef T type; };

struct A{};

template<typename T>
struct Traits{
    static const bool is_basic = true;
};

template<>
struct Traits<A>{
    static const bool is_basic = false;
};

template<typename T>
typename user_enable_if<Traits<T>::is_basic, T>::type f(T a){
    cout<<"a basic type"<<endl;
    return a;
}

template<typename T>
typename user_enable_if<!Traits<T>::is_basic, T>::type f(T a){
    cout<<"a class type"<<endl;
    return a;
}

int main(){
    A a;
    f(1);
    f(a);
}

 

在这里,我们把enable_if用到了返回值上,当传入是1时,user_enable_if<Traits::is_basic, T>::type 为T(即int),而user_enable_if<!Traits::is_basic, T>::type无法解析,因此使用第一个模板实例化。当传入为a时相反。

C++ 模板编程中,`std::enable_if` 和 `std::enable_if_t` 是用于模板元编程的重要工具,主要用于在编译时根据条件启用或禁用特定的模板函数或类。它们之间的区别主要体现在语法和使用方式上。 ### `std::enable_if` `std::enable_if` 是一个模板结构体,其定义如下: ```cpp template<bool Condition, class T = void> struct enable_if; ``` 当 `Condition` 为 `true` 时,`enable_if<Condition, T>` 会定义一个类型 `type`,其类型为 `T`。如果 `Condition` 为 `false`,则 `enable_if` 不会定义 `type`,从而导致编译器在尝试访问 `type` 时产生错误。 通常情况下,`std::enable_if` 会与其他模板参数一起使用,以实现 SFINAE(Substitution Failure Is Not An Error)机制。例如: ```cpp template<typename T> typename std::enable_if<std::is_integral<T>::value, T>::type add(T a, T b) { return a + b; } ``` 在这个例子中,只有当 `T` 是整数类型时,`std::is_integral<T>::value` 才为 `true`,此时 `std::enable_if` 会定义 `type`,从而使该函数模板有效。否则,该函数模板会被忽略。 ### `std::enable_if_t` `std::enable_if_t` 是 C++14 引入的一个别名模板,它是 `std::enable_if<Condition, T>::type` 的简化形式。其定义如下: ```cpp template<bool Condition, class T = void> using enable_if_t = typename std::enable_if<Condition, T>::type; ``` 通过使用 `std::enable_if_t`,可以简化代码并提高可读性。例如,上面的例子可以改写为: ```cpp template<typename T> std::enable_if_t<std::is_integral<T>::value, T> add(T a, T b) { return a + b; } ``` 在这个例子中,`std::enable_if_t` 直接返回 `T` 类型,而不需要显式地写 `typename std::enable_if<...>::type`。 ### 使用场景 1. **函数重载**:可以通过 `std::enable_if` 或 `std::enable_if_t` 来实现基于类型的函数重载。 2. **类模板特化**:可以在类模板中使用 `std::enable_if` 来根据条件选择不同的实现。 3. **编译时断言**:可以在编译时检查某些条件是否满足,从而避免运行时错误。 ### 示例代码 以下是一个使用 `std::enable_if_t` 的示例: ```cpp #include <type_traits> #include <iostream> template<typename T> std::enable_if_t<std::is_integral<T>::value, T> add(T a, T b) { return a + b; } template<typename T> std::enable_if_t<std::is_floating_point<T>::value, T> add(T a, T b) { return a * b; } int main() { std::cout << add(3, 4) << std::endl; // 输出 7 std::cout << add(3.0, 4.0) << std::endl; // 输出 12 return 0; } ``` 在这个示例中,`add` 函数根据传入的类型(整数或浮点数)选择不同的实现。 ### 总结 - `std::enable_if` 是一个模板结构体,适用于 C++11 及更早版本。 - `std::enable_if_t` 是 C++14 引入的别名模板,简化了 `std::enable_if` 的使用。 - 两者都可以用于实现 SFINAE 机制,但在 C++14 及以后版本中,推荐使用 `std::enable_if_t` 以提高代码的可读性和简洁性 [^2]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值