在C++ 11中is_constexpr是否可行?

use*_*370 32 c++ compile-time constexpr c++11

是否有可能根据C++ 11中的C++ 11表达式是否为常量表达式(即constexpr)生成编译时布尔值?关于SO的几个问题与此有关,但我在任何地方都没有看到直接答案.

Joh*_*itb 30

我曾写过它(编辑:见下面的限制和解释).来自/sf/answers/720131891/:

template<typename T> 
constexpr typename remove_reference<T>::type makeprval(T && t) {
  return t;
}

#define isprvalconstexpr(e) noexcept(makeprval(e))
Run Code Online (Sandbox Code Playgroud)

但是有很多种常量表达式.上面的答案检测prvalue常量表达式.


说明

noexcept(e)表达式给出了false充分必要条件e包含

  • 除非调用是常量表达式,否则可能会对没有非抛出异常规范的函数进行求值调用,
  • 可能被评估的throw表达,
  • 可能被评估的可投掷形式的dynamic_casttypeid.

请注意,函数模板makeprval未声明noexcept,因此调用需要是第一个不应用的项目符号的常量表达式,这就是我们滥用的内容.我们还需要其他子弹也不适用,但是很幸运的是,无论是throwa dynamic_cast还是throwable 还是typeid不允许使用常量表达式,所以这很好.

限制

不幸的是,有一个明显的限制,对你来说可能或不重要."潜在评估"的概念比常数表达式的限制更为保守.所以上面noexcept可能会给出假阴性.它会报告一些表达式不是prvalue常量表达式,即使它们是.例:

constexpr int a = (0 ? throw "fooled!" : 42);
constexpr bool atest = isprvalconstexpr((0 ? throw "fooled!" : 42));
Run Code Online (Sandbox Code Playgroud)

在上面atest是假的,即使初始化a成功了.这是因为作为一个常量表达式,"邪恶的"非常量子表达式"永远不会被评估"就足够了,即使这些邪恶的子表达式正式评估的.

  • @litb这对Clang不起作用,因为Clang在判断它是否是"noexcept"时不检查调用是否是常量表达式. (3认同)

Dav*_*one 16

截至2017年,is_constexpr在C++ 11中是不可能的.这听起来很奇怪,所以让我解释一下历史.

首先,我们添加了此功能来解决缺陷:http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#1129

Johannes Schaub - litb发布了一个constexpr检测宏,该宏依赖于常量表达式隐式noexcept的规定.这在C++ 11中有效,但至少从一些编译器(例如,clang)从未实现过.然后,作为C++ 17的一部分,我们评估了从C++ 17中删除不推荐的异常规范.作为该措辞的副作用,我们意外地删除了该条款.当核心工作组讨论重新加入该条款时,他们意识到这样做存在一些严重问题.您可以在LLVM错误报告中查看完整的详细信息.因此,我们决定将其视为针对所有标准版本的缺陷并追溯删除它,而不是将其添加回来.

这样做的结果是,据我所知,无法检测表达式是否可用作常量表达式.

  • @CharlesLWilcox:冒着通过解释它来杀死这个笑话的风险,我们考虑在下一个标准中添加一些内容,并在进一步审查时,将其从旧标准中删除.请参阅:该漫画的第二个小组. (2认同)
  • 我有[一个使这成为可能的提案](https://github.com/davidstone/isocpp/blob/master/constexpr-parameters.md)。它针对的是 C++23。如果被接受,我将作为答案发布。(请注意,GitHub 上的一些格式是混乱的) (2认同)

Ric*_*ith 10

是的,这是可能的.一种方法(即使最近的noexcept更改也有效)是利用C++ 11缩小转换规则:

缩小转换为一个整数型或无作用域枚举类型为整数类型不能表示原始类型的所有值的隐式转换[...],除非源是一个常量表达式,其值后积分优惠将适合进入目标类型.

(强调我的).列表初始化通常不允许缩小转换,当与SFINAE结合使用时,我们可以构建小工具来检测任意表达式是否为常量表达式:

// p() here could be anything
template<int (*p)()> std::true_type is_constexpr_impl(decltype(int{(p(), 0U)}));
template<int (*p)()> std::false_type is_constexpr_impl(...);
template<int (*p)()> using is_constexpr = decltype(is_constexpr_impl<p>(0));

constexpr int f() { return 0; }
int g() { return 0; }
static_assert(is_constexpr<f>());
static_assert(!is_constexpr<g>());
Run Code Online (Sandbox Code Playgroud)

现场演示.

这里的关键是,int{(expr, 0U)}包含从缩小转换unsigned intint(并因此是非法的构造),除非 expr是一常量表达式,在这种情况下,整个表达式(expr, 0U)是一个常数表达式,其评估值适合的类型int.


Ami*_*rsh 5

添加了 C++20std::is_constant_evaluated()

这允许检查某个表达式是否是常量计算表达式,即在编译时计算。

使用示例:

constexpr int foo(int num) {
    // below is true in case the condition is being evaluated at compile time
    // side note, using: if constexpr (std::is_constant_evaluated())
    // would be evaluated always to true, so you should use a simple if!
    if (std::is_constant_evaluated()) {
        return foo_compiletime(num);
    }
    else {
        return foo_runtime(num);
    }
}

int main() {
    constexpr auto t1 = foo(6); // reaches foo_compiletime
    const auto t2 = foo(6);     // reaches foo_compiletime
    int n = rand() % 10;
    const auto t3 = foo(n);     // reaches foo_runtime

    auto t4 = foo(6); // unfortunately, reaches foo_runtime
}
Run Code Online (Sandbox Code Playgroud)

上面示例中的最后一个调用将到达foo_runtime,因为该调用不在常量表达式上下文中(结果未用作常量表达式,另请参阅此 SO 答案)。

与将决定权留给用户的情况相比,这可能会导致不必要的悲观化,用户可能会调用:

    auto t4 = foo_compiletime(6);
Run Code Online (Sandbox Code Playgroud)

如果它被声明为函数,则允许编译器在编译时执行foo_compiletime内的操作constexpr,或者如果它被声明为 ,则编译器有义务这样做consteval。然而,一旦我们将决定权交给编译器,我们就会到达foo_runtime ,除非我们通过将结果放入,或变量中明确指示编译器转到foo_compiletime。如果用户需要帮助编译器查看正确的路径,那么在某种程度上,这就忽略了在这两种情况下使用一个函数的价值。constconstexprconstinit

要优化调用的另一个可能的选项是:

    constexpr auto temp = foo(6); // foo_compiletime
    auto t4 = temp;
Run Code Online (Sandbox Code Playgroud)

但同样,我们要求用户了解foo的内部行为,这并不是我们想要实现的目标。

请参阅此代码中的悲观化。

请参阅这篇有关该主题的精彩博客文章,了解更多相关内容