第7.3章: if constexpr

English

概述

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");
    }
}

最终程序中只保留匹配的那一个分支。

要点