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

时间:2017-10-24 20:37:22

标签: c++ c++14 c++17 constexpr

由于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)?

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

6 个答案:

答案 0 :(得分:16)

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

template <class T>
constexpr void test_helper(T&& 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有一个多年的bug,并没有正确处理这个问题。但是,确实如此。实例:http://coliru.stacked-crooked.com/a/e7b037932c358149。它打印&#34; 100&#34;,它应该。

答案 1 :(得分:8)

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,从而使测试无用。

答案 2 :(得分:4)

我认为这样做的规范方法是使用static_assertstatic_assert在编译时被评估,因此如果它们的条件为假,它们将破坏构建。

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

答案 3 :(得分:3)

如果可以使用C ++ 20,则可以使用std::is_constant_evaluated来满足您的需求。 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+),因此您可能还需要检测编译器版本。

答案 4 :(得分:1)

constexpr函数中,您无法判断是否在constexpr上下文中进行了评估。有许多建议要添加此功能。没有成功。

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

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

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

如果在编译时无法计算CONSTEXPR_EVAL( bar(foo, true) ),则bar(foo, true)将无法编译,如果可以在编译时对其进行求值,则返回该值。

涉及noexcept(在编译时评估的函数为noexcept)的其他技巧可以起作用(参见@NirFriedman's answer)。

答案 5 :(得分:0)

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

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

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

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