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

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

自从 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) 内部知道函数是在编译时执行还是在运行时执行?

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

我认为规范的方法是 static_assertstatic_asserts 在编译时进行评估,因此如果它们的条件为假,它们将破坏构建。

#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 对我来说编译得很好,表明一切都按预期工作。

列出的技术有效,但由于它使用 static_assert,因此对 sfinae 不友好。更好的方法(理论上,您会明白我的意思)是检查函数是否为 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);
}

foo 只有 noexcept 如果 x 是常量表达式,并且 b 为真;如果布尔值是假的,那么我们调用一个非 constexpr 函数,破坏我们的 constexpr-ness。那么,让我们测试一下:

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”。

很抱歉破坏了聚会,但是肯定没有标准 方法可以做到这一点。在 as-if 规则下,编译器可以发出在 运行 时间计算结果的代码,即使在已经被迫在编译时在不同上下文中计算结果的情况下也是如此。任何可以在编译时完成的事情都可以在 运行 时再次完成,对吧?而且计算已经证明不会抛出。

因此,任何符合标准的 IS_REALLY_CONSTEXPRis_really_constexpr 检查都不能反驳完全相同的调用,或者就此而言,完全相同的值 constexpr符号涉及 运行 时间计算。

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

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

constexpr 函数中,您无法判断您是否在 . Since , this functionalty was added 之前的 constexpr 上下文中被评估 - constexpr bool std::is_constant_evaluated() 会告诉您是否您在 constexpr 上下文中被调用。


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

的上下文中使用结果

假设您的 constexpr 表达式 returns 是一个非空整数或指针类型(包括函数指针):

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

如果 bar(foo, true) 不能在编译时求值,那么 CONSTEXPR_EVAL( bar(foo, true) ) 将编译失败,如果可以在编译时求值,它 returns 那个值。

涉及 noexcept 的其他技巧(在编译时评估的函数是 noexcept)可以工作(参见 )。

C++20 引入了 is_constant_evaluated,在 header <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 始终 returns 为真,使测试无用。

如果可以使用 C++20,std::is_constant_evaluated which does exactly what you want. std::is_constant_evaluated 通常使用编译器内部实现。

这在 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_asserts 处编译。见 https://www.godbolt.org/z/Tr3z93M3s