第7.3章: if constexpr
概述
if constexpr 是一种编译期条件语句。当其条件
是常量表达式时,编译器会移除未被选中的分支。
这与普通 if 不同——普通 if 总是会在程序中生成两个
分支。
本章展示 if constexpr 如何工作、如何串联
else if constexpr、它如何取代 #ifdef 的某些用途,
以及它如何帮助避免隐藏的语法错误。
1. 基础示例
如果条件是常量表达式,编译器只保留被选中的分支。
constexpr bool use_fast_path = true;
int compute(int x)
{
if constexpr(use_fast_path)
{
return x * 2; // this branch is kept
}
else
{
return x + 2; // this branch is removed
}
}
由于 use_fast_path 是 constexpr 的,编译器会
完全丢弃 else 分支。
2. 为什么这很重要
当你想做以下事情时,if constexpr 很有用:
- 在编译期选择代码路径
- 彻底移除未使用的代码
- 避免生成在某些情况下会非法的代码
与普通 if 不同,被丢弃的分支根本不会被编译。
3. if constexpr / else if constexpr / else
你可以把 if constexpr 与 else if constexpr
以及 else 串联起来。只会选中一个分支,其他所有分支
都在编译期被移除。
#include <fast_io.h>
constexpr int mode = 2;
void handle_mode()
{
using namespace ::fast_io::iomnp;
if constexpr(mode == 1)
{
print("mode 1\n");
}
else if constexpr(mode == 2)
{
print("mode 2\n");
}
else
{
print("other mode\n");
}
}
最终程序中只保留匹配的那一个分支。
4. 与 constexpr 变量配合使用
一个常见模式是用 constexpr 配置变量来控制行为。
#include <fast_io.h>
constexpr bool print_hex = false;
void print_value(int x)
{
using namespace ::fast_io::iomnp;
if constexpr(print_hex)
print("hex: ", hex(x), "\n");
else
print("dec: ", x, "\n");
}
如果 print_hex 为 false,编译器会移除
整个十六进制打印分支。
5. 取代部分 #ifdef 用法
if constexpr 与宏结合时非常有用。在很多情况下,
它可以取代函数内部的 #ifdef。思路是把宏转换为
constexpr 布尔值,然后用
if constexpr 在编译期移除未使用的分支。
#include <fast_io.h>
void foo()
{
using namespace ::fast_io::iomnp;
constexpr bool is_windows{
#ifdef _WIN32
true
#else
false
#endif
};
if constexpr(is_windows)
{
win32_foo();
}
else
{
posix_foo();
}
}
这保持了函数的可读性,并避免 #ifdef 散落在代码各处。
6. 为什么这比大段 #ifdef 更安全
#ifdef 在编译器看到代码之前就移除了代码。这可能
在被禁用的分支里隐藏语法错误或非法代码。
例如:
#ifdef _WIN32
win32_foo();
#else
foo() // missing semicolon, but never compiled on Windows
#endif
在 Windows 上,foo() 这一行被预处理器移除,因此
编译器根本不会检查它。这个错误会一直隐藏,直到有人在
其他平台上构建。
而使用 if constexpr 时,两个分支都必须是合法的 C++。
编译器会解析两者,只在确认有效之后才丢弃未使用的那个。
#include <fast_io.h>
void foo()
{
using namespace ::fast_io::iomnp;
constexpr bool is_windows{
#ifdef _WIN32
true
#else
false
#endif
};
if constexpr(is_windows)
win32_foo();
else
foo(); // must be valid C++ or compilation fails
}
这降低了隐藏编译失败的概率。
7. 用 if constexpr(false) 禁用代码
if constexpr(false) 可用于临时禁用一段代码。
被禁用的分支在编译期被移除,但编译器仍会检查其正确性。
if constexpr(false)
{
do_something(); // must still be valid C++
}
这与下面这种写法不同:
#if 0
do_something() // missing semicolon, but never checked
#endif
当程序员有意想隐藏未完成或语法非法的代码时,#if 0 仍然有用。
那种情况下,目标是让编译器根本看不到这段代码。
但当你希望编译器继续检查被禁用代码的正确性时,
if constexpr(false) 是更安全的选择。
8. 用 if constexpr 检查字节序
if constexpr 也适用于根据平台的编译期属性(例如字节序)
选择代码路径。
#include <bit>
#include <fast_io.h>
void show_endianness()
{
using namespace ::fast_io::iomnp;
if constexpr(::std::endian::little == ::std::endian::native)
{
print("native endianness: little\n");
}
else if constexpr(::std::endian::big == ::std::endian::native)
{
print("native endianness: big\n");
}
else
{
print("native endianness: mixed or unknown\n");
}
}
最终程序中只保留匹配的那一个分支。
要点
if constexpr是编译期条件语句。- 你可以把它与
else if constexpr和else串联。 - 当条件是常量表达式时,未使用的分支会被移除。
- 它可以取代函数内部部分
#ifdef的用法。 - 它能避免被禁用分支里隐藏的语法错误。
if constexpr(false)禁用代码,但仍会检查它。- 当有意隐藏非法代码时,
#if 0仍然有用。 - 它可以按平台选择代码路径,例如字节序。