编程时通过在if语句中使用constexpr关键字就可以在编译期计算if语句中的表达式,然后决定if语句走到哪个分支,没有走到的分支虽然编译器也会对这部分的代码进行代码走查,但其实这些代码最终可能不会被生成或者说被编译器丢弃。如下面这段代码所示:
template <typename T>
std::string ToString(T x){
if constexpr(std::is_same_v<T, std::string>) {
return x; //如果是字符串走到这个分支
}
else if constexpr(std::is_arithmetic_v<T>) {
return std::to_string(x); //如果是数值型走到这个分支
}
else {
return std::string(x);
}
}
如上面的代码,传入的参数类型T会在编译期和if各分支语句中的类型相比较,如果is_same_v返回值不为真,这条语句可能就会被编译器丢弃掉。
1 编译器if语句缘起
在上面的示例代码中,如果将if表达式前的constexpr关键字去掉,然后在对模版做如下实例化时,再次对程序进行编译和执行会产生什么效果呢?实例化代码如下:
int main()
{
cout<<ToString(42)<<endl;
cout<<ToString("Hello World")<<endl;
return 0;
}
点击编译,编译器将会报错,报错内容为:
从上图可以看出,传入类型为整型时,会使代码在if语句和else语句后的表达式无效从而导致编译器失败。失败的原因是什么呢?
这是因为在去掉了constexpr关键字后,实例化模板时编译器会将整个模板函数作为一个整体,if语句表达式检查又是运行时特性,即使在模板函数中if语句表达式为false也要能够通过编译才行。
现在就可以理解了,加上constexpr之所以能够通过编译,是因为在编译期对于表达式的值进行计算后,如果为false就不会生成该段代码,所以能通过编译并输出正确结果。
还有一点需要注意的就是:即使在编译期部分分支代码被丢弃,但是也必须满足语法正确。
2 使用编译期 if 语句
原则上可以在所有的if语句中使用表达式,但是也有限制,既不能将它代替预编译指令,不能在函数体之外进行使用。
2.1 编译期if语句影响函数返回值
如下面的代码所示,代码编译没有问题,但是在运行时函数结果返回会导致不确定性。
template <typename T>
auto GetValue(T t){
if constexpr(std::is_same_v<T,std::string>){
return "Success";
}
else{
return 0;
}
}
上面这段代码在编译时总是成功的,但是运行时改函数调用每次返回的结果类型是不一样的,如果传入一个string型,将返回字符串,其他则返回0这个整型数据。
2.2 编译期if语句返回值return不能省
编译器if语句不能省略else语句的返回值,否则可能导致编译器报错。如下面的代码就有可能会产生上面的问题:
template <typename T>
auto GetValue(T t){
if constexpr(sizeof(int)==4){
return "hello";
}
return 1;
}
int main()
{
cout<<GetValue(34)<<endl;
return 0;
}
上面的代码传入一个整型数据时,会同时满足函数中if语句和if之外的返回语句,因此编译器会因为同时返回两个类型而报错。报错内容为:
只需要稍微改动,就可以将上面的代码通过编译器并运行出正确的结果。如下代码所示:
template <typename T>
auto GetValue(T t){
if constexpr(sizeof(int)==4){
return "hello";
}
else{
return 1;
}
}
通过比较可以得出,运行时的if语句可以将else省略放到外面,但是编译期的if语句不能这么使用,因为这可能导致函数返回两个不同的类型返回值从而导致编译失败。
2.3 编译期if语句中的复杂表达式
之前写代码时,习惯在if语句中进行&&、||的表达式运算,但如果在编译期if语句中还这么写的话可能就会导致错误了。因此,如果想要使用编译期if语句达到和运行时if语句相同的效果,就需要把if语句中的表达式进行拆分改成if嵌套语句进行使用。之所以这么做也是因为,在编译期if语句中编译时判断的是if语句的整体,需要所有的语法格式都正确,才能通过编译。就像下面这段代码一样。
template <typename T>
auto GetValue(T t){
if constexpr (std::is_integral<T>::value && T{} < 10) {
return t + 2;
}
return t;
}
如上面的代码,如果传入一个整型数据时能够正常编译且输出t+2的结果,当传入一个字符串时编译器就会报错。如做如下实例化时:
int main()
{
cout<<GetValue("24")<<endl;
return 0;
}
报错结果如下:
3 带初始化的编译期if语句
惊喜吧,if语句中也可以进行初始化了,使用方法也很简单,方法如下:
using namespace std;
template <typename T>
auto GetValue(T t){
if constexpr (std::is_integral<T>::value) {
if constexpr(auto obj=t;std::is_same_v<decltype(obj),T>){
cout<<"obj和T类型一致"<<endl;
return 1;
}
else{
cout<<"obj和T类型不一致"<<endl;
return 2;
}
}
else{
return "hello";
}
}
上面的代码就是在编译期if语句中对局部变量进行初始化并判断类型,然后输出不同的结果,当实例化代码如下所示时,它的输出结果和预期的是一致的。
int main()
{
cout<<GetValue(12)<<endl;
return 0;
}
输出结果为:
obj和T类型一致
1
4 普通函数中使用编译期if语句
if constexpr 可以在任何函数中使用,需要注意的是在普通函数中使用的时候需要保证if语句的各分支语句都是正确的,否则也会导致编译错误。如下面的代码就不能通过编译:
int main()
{
if constexpr(std::numeric_limits<char>::is_signed) {
static_assert(std::numeric_limits<char>::is_signed);
}
else {
static_assert(!std::numeric_limits<char>::is_signed);
}
return 0;
}
上面这段代码是永远不会通过编译器编译的,因为在这段代码里同时只会有有一个断言是有效的。由此也能得出结果,在上面的模板示例中使用编译期if语句会将无效的代码丢弃,但是在普通函数中计时条件为假、语法正确也是不会丢弃的。这一点也是使用时需要注意的地方。
5 总结
本文是对编译器if语句做了一个理论上全面的介绍,可能还有一些没有说到的地方,欢迎大家留言评论。谢谢。
扫码关注腾讯云开发者
领取腾讯云代金券
Copyright © 2013 - 2025 Tencent Cloud. All Rights Reserved. 腾讯云 版权所有
深圳市腾讯计算机系统有限公司 ICP备案/许可证号:粤B2-20090059 深公网安备号 44030502008569
腾讯云计算(北京)有限责任公司 京ICP证150476号 | 京ICP备11018762号 | 京公网安备号11010802020287
Copyright © 2013 - 2025 Tencent Cloud.
All Rights Reserved. 腾讯云 版权所有