有可能知道constexpr什么时候真的是constexpr?

LeD*_*YoM 21 c++ constexpr c++14 c++17

由于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;
}
Run Code Online (Sandbox Code Playgroud)

当然,结果是:

7
7
7
Run Code Online (Sandbox Code Playgroud)

到现在为止还挺好.所以我的问题是:如果函数在编译时或运行时执行,有没有办法(可能是标准的)知道foo(const int s)?

编辑:也可以在运行时知道在编译时是否评估了函数?

Nir*_*man 18

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

template <class T>
constexpr void test_helper(T&& t) {}

#define IS_CONSTEXPR(...) noexcept(test_helper(__VA_ARGS__))
Run Code Online (Sandbox Code Playgroud)

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

所以现在让我们定义一下:

double bar(double x) { return x; }

constexpr double foo(double x, bool b) {
    if (b) return x; 
    else return bar(x);
}
Run Code Online (Sandbox Code Playgroud)

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;
Run Code Online (Sandbox Code Playgroud)

编译,太棒了!这给了我们编译时布尔值(不是编译失败),例如可以用于sfinae.

抓到了吗?那么,clang有一个多年的bug,并没有正确处理.但是,确实如此.实例:http://coliru.stacked-crooked.com/a/e7b037932c358149.它应该打印"100".

  • @JohannesSchaub-litb 是的,我不确定。我还考虑了字面上的解释:指令是在编译时还是运行时由 CPU 执行。有点不清楚,这是这些问题之一,如果OP告诉我们他们的结局是什么可能会更好。我想我会保留这个答案,因为它是一种合理的解释,并且它确实包含有用的信息。 (2认同)
  • 这在 gcc 8.2 和 9.1 之间发生了问题。打印 000。Clang 仍然是 000。MSVC 具有正确的输出。 (2认同)

pet*_*gel 7

我认为这样做的规范方法是使用static_assert. static_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;
}
Run Code Online (Sandbox Code Playgroud)

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

  • 这里的问题是,这绝对迫使编译器在编译时尽其所能来评估 constexpr。但它没有说明当您在非 static_assert 上下文中调用该函数时编译器是否实际上正在执行此操作。它只是告诉您编译器如果愿意的话**可以**做到这一点。我有一个编译器(旧版本的 gcc)在编译时不费心去评估 `constexpr` 函数,即使它完全有能力这样做。 (2认同)

L. *_* F. 7

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 */;
}
Run Code Online (Sandbox Code Playgroud)

请注意,此处使用普通if代替if constexpr。如果使用if constexpr,则必须在编译时评估条件,因此is_constant_evaluated始终返回true,从而使测试无用。

  • @ÖöTiib您在非“constexpr”上下文中调用“foo”,因此该函数正确地表明它是在运行时评估的。 (2认同)

Yak*_*ont 6

constexpr函数中,您可以判断您是否在constexpr之前的上下文中被评估。从添加了此功能-会告诉您是否在上下文中被调用。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
Run Code Online (Sandbox Code Playgroud)

那么CONSTEXPR_EVAL( bar(foo, true) )将无法编译,如果bar(foo, true)不能在编译时进行评估,如果它可以在编译时计算它返回的值。

其他涉及noexcept(在编译时评估的函数是noexcept)的技巧也可以工作(请参阅@NirFriedman 的回答)。


Jan*_*tke 5

如果您可以使用 C++20,则std::is_constant_evaluated可以完全满足您的需求。std::is_constant_evaluated通常使用编译器内部实现。

__builtin_is_constant_evaluated在 GCC 和 clang 中被调用,因此您可以围绕它实现自己的“安全”包装器,即使在 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
}
Run Code Online (Sandbox Code Playgroud)

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