F-3 constexpr 函数(第三部分)与 consteval
强制在编译时求值 constexpr 函数
无法告知编译器应优先在编译时求值 constexpr 函数(例如当 constexpr 函数的返回值用于非常量表达式时)。
但我们可通过确保返回值在需要常量表达式的位置被使用,来强制符合条件的constexpr函数在编译时实际求值。此操作需针对每次调用单独执行。
最常见的方法是使用返回值初始化常量表达式变量(此即先前示例中使用变量'g'的原因)。遗憾的是,这种方法需要为确保编译时求值而引入新变量,既影响代码美观度又降低了可读性。
然而在C++20中,我们即将介绍更优的解决方案。
consteval特性(C++20)
C++20引入consteval关键字,用于强制函数在编译时求值,否则将引发编译错误。此类函数称为立即函数immediate functions。
#include <iostream>
consteval int greater(int x, int y) // function is now consteval
{
return (x > y ? x : y);
}
int main()
{
constexpr int g { greater(5, 6) }; // ok: will evaluate at compile-time
std::cout << g << '\n';
std::cout << greater(5, 6) << " is greater!\n"; // ok: will evaluate at compile-time
int x{ 5 }; // not constexpr
std::cout << greater(x, 6) << " is greater!\n"; // error: consteval functions must evaluate at compile-time
return 0;
}

在上例中,前两次对 greater() 的调用将在编译时求值。而 greater(x, 6) 无法在编译时求值,因此会引发编译错误。
最佳实践
若函数因特定原因必须在编译时求值(例如执行仅能在编译时完成的操作),请使用 consteval。
值得注意的是,consteval函数的形参并非constexpr(尽管consteval函数本身仅能在编译时求值)。此设计出于一致性考虑。
判断constexpr函数调用在编译时或运行时求值
C++目前尚未提供可靠的判断机制。
那么 std::is_constant_evaluated 或 consteval 呢?(进阶)
这两种能力均无法告知函数调用是在编译时还是运行时进行求值。
std::is_constant_evaluated()(定义于 <type_traits> 头文件)返回一个 bool 值,指示当前函数是否在常量求值上下文中执行。常量求值上下文constant-evaluated context(亦称常量上下文constant context)定义为需要常量表达式的场景(如 constexpr 变量的初始化)。因此当编译器必须在编译时求值常量表达式时,std::is_constant_evaluated() 将如预期返回 true。
此设计旨在支持如下操作:
#include <type_traits> // for std::is_constant_evaluated()
constexpr int someFunction()
{
if (std::is_constant_evaluated()) // if evaluating in constant context
doSomething();
else
doSomethingElse();
}
然而,编译器也可能在不需要常量表达式的上下文中选择在编译时求值 constexpr 函数。在这种情况下,即使函数确实已在编译时被求值,std::is_constant_evaluated() 仍会返回 false。因此 std::is_constant_evaluated() 的真实含义是“编译器被迫在编译时进行求值”,而非“该函数在编译时被求值”。
关键洞见
虽然这看似奇怪,但存在以下原因:
正如提出该特性的论文所指出的,标准实际上并未区分“编译时”与“运行时”。若要定义涉及此区别的行为,将需要更大幅度的变更。
优化不应改变程序的可观察行为(除非标准明确允许)。如果因任何原因导致函数在编译时被求值时,std::is_constant_evaluated() 返回 true,那么优化器决定在编译时而非运行时求值函数,就可能改变该函数的可观察行为。结果是,程序的行为可能因编译时的优化级别而产生巨大差异!
虽然可通过多种方式解决此问题,但这些方案会增加优化器的复杂性,并/或限制其对特定场景的优化能力。
C++23引入的consteval语法可替代if (std::is_constant_evaluated()),不仅提供更优雅的表达方式,还修复了其他问题。但其评估机制与 if (std::is_constant_evaluated()) 完全相同。
使用 consteval 实现 constexpr 函数的编译时执行(C++20)
consteval 函数的缺点在于无法在运行时评估,这使其灵活性不及既能编译时求值又能运行时求值的 constexpr 函数。因此,仍需一种便捷方式强制 constexpr 函数在编译时求值(即使返回值使用场景并不要求常量表达式),以便在可能时显式强制编译时求值,在不可行时保留运行时求值。
以下示例展示了实现方法:
#include <iostream>
#define CONSTEVAL(...) [] consteval { return __VA_ARGS__; }() // C++20 version per Jan Scultke (https://stackoverflow.com/a/77107431/460250)
#define CONSTEVAL11(...) [] { constexpr auto _ = __VA_ARGS__; return _; }() // C++11 version per Justin (https://stackoverflow.com/a/63637573/460250)
// This function returns the greater of the two numbers if executing in a constant context
// and the lesser of the two numbers otherwise
constexpr int compare(int x, int y) // function is constexpr
{
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'; // will execute at runtime and return 5
std::cout << compare(5, 6) << '\n'; // may or may not execute at compile-time, but will always return 5
std::cout << CONSTEVAL(compare(5, 6)) << '\n'; // will always execute at compile-time and return 6
return 0;
}
对于进阶读者
此处利用可变参数variadic预处理宏(#define、...及__VA_ARGS__)定义常量表达式lambda,并通过尾随括号实现即时调用。可变参数宏相关信息详见:https://en.cppreference.com/w/cpp/preprocessor/replace
第20.6节——引入lambda表达式(匿名函数)将介绍lambda表达式。
以下方案同样可行(且更简洁,无需预处理器宏):
针对gcc用户
GCC 14及后续版本存在缺陷:当启用任何优化级别时,以下示例将产生错误结果。
#include <iostream>
// Uses abbreviated function template (C++20) and `auto` return type to make this function work with any type of value
// See 'related content' box below for more info (you don't need to know how these work to use this function)
// We've opted to use an uppercase name here for consistency with the prior example, but it also makes it easier to see the call
consteval auto CONSTEVAL(auto value)
{
return value;
}
// This function returns the greater of the two numbers if executing in a constant context
// and the lesser of the two numbers otherwise
constexpr int compare(int x, int y) // function is constexpr
{
if (std::is_constant_evaluated())
return (x > y ? x : y);
else
return (x < y ? x : y);
}
int main()
{
std::cout << CONSTEVAL(compare(5, 6)) << '\n'; // will execute at compile-time
return 0;
}
此为Clang21编译器的提示


由于consteval函数的实参始终被显式恒常量求值,若将constexpr函数作为consteval函数的实参调用,该constexpr函数必须在编译时完成求值!consteval函数随后将constexpr函数的结果作为自身返回值,供调用方使用。
请注意 consteval 函数采用值传递方式返回。虽然在运行时这可能效率低下(若返回值类型复制成本高昂,如 std::string),但在编译时环境中并不重要,因为整个 consteval 函数调用将被替换为预先计算的返回值。
对于进阶读者
第10.9节——函数类型推导将讲解自动返回类型。
第11.8节——带多种模板类型的函数模板 将介绍简写函数模板(auto形参)。

浙公网安备 33010602011771号