34

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

4

5 回答 5

33

我曾经写过它(编辑:见下文的限制和解释)。来自https://stackoverflow.com/a/10287598/34509

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

#define isprvalconstexpr(e) noexcept(makeprval(e))

然而,常量表达式有很多种。上面的答案检测纯右值常量表达式。


解释

noexcept(e)表达式给出当false且仅当e包含

  • 对没有非抛出异常规范的函数的潜在评估调用,除非调用是常量表达式,
  • 一个可能被评估的throw表达式,
  • dynamic_castor的一个潜在评估的可抛出形式typeid

注意函数模板makeprval没有声明noexcept,所以调用需要是一个常量表达式,第一个子弹不适用,这是我们滥用的。我们还需要其他项目符号不适用,但谢天谢地,athrow和 throwable dynamic_castortypeid也不允许在常量表达式中使用,所以这很好。

限制

不幸的是,有一个微妙的限制,这对您来说可能很重要,也可能无关紧要。“潜在评估”的概念比常量表达式应用的限制要保守得多。所以上面noexcept可能会给出假阴性。它将报告某些表达式不是纯右值常量表达式,即使它们是。例子:

constexpr int a = (0 ? throw "fooled!" : 42);
constexpr bool atest = isprvalconstexpr((0 ? throw "fooled!" : 42));

在上面atest是假的,即使初始化a成功了。这是因为作为一个常量表达式,“邪恶的”非常量子表达式“从不评估”就足够了,即使这些邪恶的子表达式在形式上潜在的评估。

于 2012-11-09T09:14:14.073 回答
21

截至 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 的一部分,我们评估了Removing Deprecated Exception Specifications from C++17。作为该措辞的副作用,我们不小心删除了该条款。当核心工作组讨论重新添加该条款时,他们意识到这样做存在一些严重问题。您可以在LLVM 错误报告中查看完整的详细信息。因此,我们决定将其视为对所有标准版本的缺陷,而不是重新添加,并追溯删除它。

这样做的效果是,据我所知,没有办法检测表达式是否可用作常量表达式。

于 2017-11-28T18:05:54.357 回答
12

是的,这是可能的。一种方法(即使最近的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>());

现场演示

这里的关键是int{(expr, 0U)}包含从unsigned intto的缩小转换int(因此格式不正确),除非 expr是一个常量表达式,在这种情况下,整个表达式(expr, 0U)是一个常量表达式,其评估值适合 type int

于 2018-05-04T07:05:13.467 回答
2

以下是用于 C++11 和 C++17的is_constexpr for functions的实现,而不是任意表达式。但是,它要求您要测试的函数的参数是默认可构造的。

#include <type_traits>

struct A {};  // don't make it too easy, use a UDT

          A f1(A a) { return a; }  // is_constexpr -> false
constexpr A f2(A a) { return a; }  // is_constexpr -> true

// The following turns anything (in our case a value of A) into an int.
// This is necessary because non-type template arguments must be integral 
// (likely to change with C++20).
template <class T> constexpr int make_int(T &&) { return 0; }

// Helper to turn some function type (e.g. int(float)) into a function
// pointer type (e.g. int (*)(float)).
template <class T> struct signature_from;
template <class R, class... Args> struct signature_from<R(Args...)> {
    using type = R(*)(Args...);
};

// See std::void_t for the idea. This does it for ints instead of types.
template <int...> using void_from_int = void;

// The fallback case: F is not a function pointer to a constexpr function
template <class T, typename signature_from<T>::type F, class = void_from_int<>>
struct is_constexpr {
    static constexpr bool value = false;
};
// If void_from_int<make_int(F(Args()...))> doesn't lead to a substitution
// failure, then this is the preferred specialization. In that case F must
// be a function pointer to a constexpr function. If it is not, it could
// not be used in a template argument.
template <class R, class... Args, typename signature_from<R(Args...)>::type F>
struct is_constexpr<R(Args...), F, void_from_int<make_int(F(Args()...))>>
{
    static constexpr bool value = true;
};

// proof that it works:
static_assert(!is_constexpr<A(A), f1>::value, "");
static_assert( is_constexpr<A(A), f2>::value, "");

#if __cplusplus >= 201703
// with C++17 the type of the function can be deduced:
template<auto F> struct is_constexpr2 : is_constexpr<std::remove_pointer_t<decltype(F)>, F> {};

static_assert(!is_constexpr2<f1>::value, "");
static_assert( is_constexpr2<f2>::value, "");
#endif

在https://godbolt.org/g/rdeQme上查看它的实际应用。

于 2018-04-20T14:04:48.870 回答
2

添加了 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
}

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

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

    auto t4 = foo_compiletime(6);

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

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

    constexpr auto temp = foo(6); // foo_compiletime
    auto t4 = temp;

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

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

在这篇关于该主题的精彩博客文章中查看更多信息。

于 2021-05-31T14:55:43.520 回答