目前 无法 通过任何关键字向编译器“建议”:只要可行,就优先在编译期求值某个 constexpr 函数。唯一可靠的做法是:把返回值用在需要常量表达式的上下文中,从而迫使编译期求值;但这需要逐调用地手动处理。
最常见的手法是用返回值初始化一个 constexpr 变量(我们在前面示例中一直使用变量 g)。然而,这种做法必须额外引入一个变量,代码显得累赘且可读性下降。
(高级读者)
社区曾提出过若干“奇技淫巧”来避免每次都声明 constexpr 变量,详见 此处 与 此处。
从 C++20 开始,这一问题有了更优雅的解决方案,下文即将介绍。
C++20 的 consteval
C++20 引入关键字 consteval,用于声明立即函数(immediate function):此类函数必须在编译期求值,否则产生编译错误。
#include <iostream>
consteval int greater(int x, int y)   // 立即函数
{
    return (x > y ? x : y);
}
int main()
{
    constexpr int g{ greater(5, 6) };              // OK:编译期求值
    std::cout << g << '\n';
    std::cout << greater(5, 6) << " is greater!\n"; // OK:编译期求值
    int x{ 5 };                                    // 非 constexpr
    std::cout << greater(x, 6) << " is greater!\n"; // 错误:不能在运行期调用
    return 0;
}
前两处调用均满足编译期求值要求;第三处因实参 x 非编译期常量,导致编译失败。
最佳实践
若某段逻辑必须在编译期完成(例如只能编译期执行的操作),请使用 consteval。
值得注意的是,consteval 函数的形参并非 constexpr,这是为了与既有规则保持一致:函数仍可在编译期被调用,但形参本身并不隐含常量表达式属性。
判断一次 constexpr 函数调用在编译期还是运行期执行
C++ 目前没有可靠机制来直接探知某次 constexpr 函数调用究竟发生在编译期还是运行期。
std::is_constant_evaluated 与 if consteval(高级话题)
- std::is_constant_evaluated()(定义于- <type_traits>)返回- bool,指示当前是否处于常量求值上下文(constant-evaluated context),即标准中要求常量表达式的场景(如- constexpr变量初始化)。
- 设计初衷是允许在函数内部按求值环境选择不同实现:
#include <type_traits>
constexpr int someFunction()
{
    if (std::is_constant_evaluated())   // 若处于常量求值上下文
        return doSomething();           // 编译期路径
    else
        return doSomethingElse();       // 运行期路径
}
然而,编译器也可能在非强制上下文中自愿编译期求值;此时 std::is_constant_evaluated() 仍返回 false,尽管实际已编译期执行。因此,它真正表达的是“标准强制编译期求值”,而非“实际是否编译期求值”。
关键要点
标准本身并不区分“编译期”与“运行期”;若 std::is_constant_evaluated() 在任意编译期求值场景都返回 true,优化器改为编译期求值就可能改变可观察行为,从而违反“优化不改变行为”原则。
C++23 引入的 if consteval 提供了更简洁的语法并修正了部分问题,但其判定逻辑与 std::is_constant_evaluated() 相同。
借助 consteval 强制 constexpr 编译期求值(C++20)
consteval 函数无法在运行期求值,灵活性低于 constexpr。我们仍希望有一种便捷手段能在必要时强制 constexpr 函数编译期求值,而在不可能时回退到运行期。
以下示例展示了可行做法:
#include <iostream>
// C++20 版:立即调用的 consteval lambda(Jan Schultke)
#define CONSTEVAL(...) [] consteval { return __VA_ARGS__; }()
// C++11 版:借助 constexpr 变量(Justin)
#define CONSTEVAL11(...) [] { constexpr auto _ = __VA_ARGS__; return _; }()
constexpr int compare(int x, int y)
{
    if (std::is_constant_evaluated())
        return (x > y ? x : y);   // 编译期路径
    else
        return (x < y ? x : y);   // 运行期路径
}
int main()
{
    int x{ 5 };
    std::cout << compare(x, 6) << '\n';           // 运行期,输出 5
    std::cout << compare(5, 6) << '\n';           // 可能运行期,输出 5
    std::cout << CONSTEVAL(compare(5, 6)) << '\n'; // 强制编译期,输出 6
    return 0;
}
(高级读者) 上述代码利用可变参宏与立即调用的 consteval lambda 实现;宏与 lambda 相关内容分别见 宏替换 与《Lambda 表达式》。
GCC 用户注意:GCC 14 起存在优化缺陷,下列无宏版本在开启任何级别优化时可能给出错误结果:
#include <iostream>
consteval auto CONSTEVAL(auto value) { return value; }
int main()
{
    std::cout << CONSTEVAL(compare(5, 6)) << '\n'; // 强制编译期求值
    return 0;
}
由于 consteval 函数的实参始终处于显式常量求值上下文,当把 constexpr 函数作为实参传入时,该 constexpr 函数必须编译期求值;随后 consteval 函数把结果返回给调用者。
注意:返回方式为按值返回。在编译期上下文中,整个调用会被结果替换,因此即使返回类型复制成本较高(如 std::string)也无关紧要。
(高级读者)
- auto返回类型见《函数类型推导》。
- 简写函数模板(auto形参)见《多模板类型函数模板》。
