是否可以测试constexpr函数是否在编译时求值

Is it possible to test if a constexpr function is evaluated at compile time?

本文关键字:是否 编译 函数 测试 constexpr      更新时间:2023-10-16

由于constexpr的扩展版本(我认为来自C++14),您可以声明constexpr函数;真实的";constexpr。也就是说,代码在编译时执行,或者可以表现为内联函数。所以什么时候可以有这个程序:

#include <iostream>
constexpr int foo(const int s) {
return s + 4;
}
int main()
{
std::cout << foo(3) << std::endl;
const int bar = 3;
std::cout << foo(bar) << std::endl;
constexpr int a = 3;
std::cout << foo(a) << std::endl;
return 0;
}

结果是:

7
7
7

到目前为止还不错。

有没有一种方法(可能是标准的)可以在foo(const int s)内部知道函数是在编译时还是在运行时执行的?

编辑:还有可能在运行时知道函数是否在编译时求值吗?

C++20引入了is_constant_evaluated,它在头<type_traits>中定义,解决了这个问题。

constexpr int foo(int s)
{
if (std::is_constant_evaluated()) // note: not "if constexpr"
/* evaluated at compile time */;
else
/* evaluated at run time */;
}

注意,这里使用普通的if来代替if constexpr。如果使用if constexpr,则必须在编译时评估条件,因此is_constant_evaluated总是返回true,从而使测试变得无用。

列出的技术有效,但由于它使用static_assert,因此对sfinae不友好。一个更好的方法(理论上,你会明白我的意思)是检查一个函数是否是noexcept。为什么?因为,常量表达式总是noexcept,即使函数没有标记为noexcept。因此,考虑以下代码:

template <class T>
constexpr void test_helper(T&&) {}
#define IS_CONSTEXPR(...) noexcept(test_helper(__VA_ARGS__))

test_helperconstexpr,所以只要它的参数是,它就会是一个常量表达式。如果它是常量表达式,它将是noexcept,但否则它就不会是(因为它没有标记为这样)。

现在让我们定义一下:

double bar(double x) { return x; }
constexpr double foo(double x, bool b) {
if (b) return x; 
else return bar(x);
}

如果x是常数表达式,并且b为真,则foo仅为noexcept;如果布尔值为false,那么我们调用一个非constexpr函数,破坏我们的constexpr。所以,让我们测试一下:

double d = 0.0;
constexpr auto x = IS_CONSTEXPR(foo(3.0, true));
constexpr auto y = IS_CONSTEXPR(foo(3.0, false));
constexpr auto z = IS_CONSTEXPR(foo(d, true));
std::cerr << x << y << z;

它编译得很好!这为我们提供了编译时布尔值(而不是编译失败),例如,它可以用于sfinae。

捕获?嗯,clang有一个多年的错误,不能正确处理这个问题。然而,gcc确实如此。现场示例:http://coliru.stacked-crooked.com/a/e7b037932c358149.它应该打印"100"。

constexpr函数中,您无法判断是否在c++20之前的constexpr上下文中对您进行求值。从c++20开始,添加了这个函数——constexpr bool std::is_constant_evaluated()将告诉您是否在constexpr上下文中被调用。


constexpr函数之外,有多种方法可以确定是否会在constexpr上下文中评估对具有特定参数集的函数的调用。最简单的方法是在需要constexpr的上下文中使用结果。

假设您的constexpr表达式返回非空整数或指针类型(包括函数指针):

#define CONSTEXPR_EVAL(...) 
std::integral_constant< 
std::decay_t<decltype(__VA_ARGS__)>, 
__VA_ARGS__ 
>::value

如果bar(foo, true)不能在编译时求值,则CONSTEXPR_EVAL( bar(foo, true) )将无法编译,并且如果可以在编译时对其求值,则返回该值。

其他涉及noexcept(编译时评估的函数为noexcept)的技巧也可以奏效(参见@NirFriedman的回答)。

我认为实现这一点的规范方法是使用static_assertstatic_assert在编译时进行求值,因此如果条件为false,它们将破坏构建。

#include <iostream>
constexpr int foo(const int s) {
return s + 4;
}
int main()
{
std::cout << foo(3) << std::endl;
const int bar = 3;
std::cout << foo(bar) << std::endl;
constexpr int a = 3;
std::cout << foo(a) << std::endl;
static_assert(foo(3) == 7, "Literal failed");
static_assert(foo(bar) == 7, "const int failed");
static_assert(foo(a) == 7, "constexpr int failed");
return 0;
}

clang++ -std=c++14 so1.cpp对我来说编译得很好,表明一切都按预期进行。

如果您可以使用C++20,那么std::is_constant_evaluated正是您想要的。CCD_ 36通常使用内部编译器来实现。

这在GCC和clang中被称为__builtin_is_constant_evaluated,因此您可以实现自己的";"安全";即使在C++17及更低版本中,也可以将其包装起来。

// if C++20, we will need a <type_traits> include for std::is_constant_evaluated
#if __cplusplus >= 202002L
#include <type_traits>
#endif
constexpr bool is_constant_evaluated() {
#if __cplusplus >= 202002L
return std::is_constant_evaluated();
#elif defined(__GNUC__) // defined for both GCC and clang
return __builtin_is_constant_evaluated();
#else
// If the builtin is not available, return a pessimistic result.
// This way callers will implement everything in a constexpr way.
return true;
#endif
}

请注意,此内建仍然相对较新(GCC 9.0+),因此您可能还需要检测编译器版本。

基于本次讨论中的信息,我精心制作了以下最小示例:

template <class T>
constexpr void test_helper(T &&) {}
#define IS_CONSTEXPR(...) noexcept(test_helper(__VA_ARGS__))
constexpr void test(){
static_assert(IS_CONSTEXPR(10), "asdfadsf");
constexpr const int x = 10;
static_assert(IS_CONSTEXPR(x), "asdfadsf");
int y;
static_assert(IS_CONSTEXPR(y), "asdfadsf");
}
int main(){
test();
return 0;
}

令我失望的是,它未能在每个static_assels上进行编译。看见https://www.godbolt.org/z/Tr3z93M3s

很抱歉破坏了聚会,但肯定没有标准的方法。在假设规则下,编译器可以发出在运行时计算结果的代码,即使在这种情况下,编译器已经被迫在不同的上下文中在编译时计算结果。任何可以在编译时完成的操作都可以在运行时再次完成,对吧?而且这个计算已经被证明是不会抛出的。

因此,通过扩展,任何符合标准的IS_REALLY_CONSTEXPRis_really_constexpr检查都不能证明完全相同的调用,或者完全相同的constexpr符号的值涉及运行时计算。

当然,通常没有任何理由在运行时重复可以在编译时完成甚至已经完成的计算,但问题是要判断编译器是否使用了预先计算的结果,而且没有。

现在,您确实说过可能是标准,所以实际上,您最好的选择可能是用您选择的编译器测试所提供的解决方案之一,并希望它的行为一致。(或者阅读源代码,如果它是开放/公共源代码,并且你很喜欢。)