C++ 在C+中是否可以使用constexpr+;11?

C++ 在C+中是否可以使用constexpr+;11?,c++,c++11,compile-time,constexpr,C++,C++11,Compile Time,Constexpr,是否可以根据C++11表达式是否为C++11中的常量表达式(即constexpr)生成编译时布尔值?关于SO的一些问题与此相关,但我在任何地方都看不到直接的答案。我曾经写过它(编辑:有关限制和解释,请参见下文)。发件人: 在上面的atest中,即使a的初始化成功,也为false。这是因为,作为一个常量表达式,“邪恶”的非常量子表达式“从不求值”就足够了,即使那些邪恶的子表达式可能会被正式求值 截至2017年,在C++11中是不可能的。这听起来很奇怪,所以让我来解释一下历史 首先,我们添加了此功能

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

我曾经写过它(编辑:有关限制和解释,请参见下文)。发件人:


在上面的
atest
中,即使
a
的初始化成功,也为false。这是因为,作为一个常量表达式,“邪恶”的非常量子表达式“从不求值”就足够了,即使那些邪恶的子表达式可能会被正式求值

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

首先,我们添加了此功能以解决一个缺陷:

Johannes Schaub-litb发布了一个constexpr检测宏,该宏依赖于常量表达式隐式为noexcept的规定。这在C++11中有效,但至少有些编译器(例如,clang)从未实现过。然后,作为C++17的一部分,我们进行了评估。作为该措辞的副作用,我们无意中删除了该条款。当核心工作组讨论重新加入该条款时,他们意识到这样做存在一些严重问题。您可以在中看到完整的详细信息。因此,与其将其重新添加到中


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

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

#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
#包括
结构A{};//不要太简单,使用UDT
f1(aa){returna;}//is_constexpr->false
constexpr A f2(A A){返回A;}//is_constexpr->true
//下面将任何内容(在本例中为a值)转换为int。
//这是必需的,因为非类型模板参数必须是整数
//(可能会随着C++20而改变)。
模板constexpr int make_int(T&){return 0;}
//帮助程序将某些函数类型(例如int(float))转换为函数
//指针类型(例如int(*)(float))。
模板结构签名来自;
模板结构签名\u来自{
使用type=R(*)(Args…);
};
//请参见std::void\t了解想法。这样做是为了int而不是类型。
使用void_from_int=void的模板;
//回退情况:F不是指向constexpr函数的函数指针
模板
结构是_constexpr{
静态constexpr bool值=false;
};
//如果void_from_int不导致替换
//失败,那么这是首选的专门化。在这种情况下,F必须
//是指向constexpr函数的函数指针。如果不是,它可能会
//不能在模板参数中使用。
模板
结构是_constexpr
{
静态constexpr布尔值=真;
};
//证明其有效的证据:

静态断言(!is_constexpr.

是的,这是可能的。一种方法(即使在最近的
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>());
//p()这里可以是任何东西
模板std::true_类型是_constexpr_impl(decltype(int{(p(),0U)}));
模板std::false_类型为_constexpr_impl(…);
使用is_constexpr=decltype的模板(is_constexpr_impl(0));
constexpr int f(){return 0;}
int g(){return 0;}
静态断言(is_constexpr());
静态断言(!is_constexpr());

这里的关键是
int{(expr,0U)}
包含从
unsigned int
int
的窄化转换(因此格式错误),除非
expr
是一个常量表达式,在这种情况下,整个表达式
(expr,0U)
是一个常量表达式,其计算值适合添加的类型
int

C++20 这允许检查某个表达式是否为常量计算表达式,即在编译时进行计算

用法示例:

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_运行时,因为该调用不在常量表达式上下文中(结果未用作常量表达式,另请参见)

与将决策权留给用户的情况相比,这可能会导致不必要的悲观情绪,用户可能会呼叫:

    auto t4 = foo_compiletime(6);
如果声明为
constexpr
函数,编译器可以在编译时执行foo_compiletime内的操作,或者如果声明为
consteval
,则必须执行该操作。但是,一旦我们将决定权交给编译器,我们将到达foo_运行时,除非我们显式地将编译器指向go对于foo_compiletime,通过将结果放入
const
constexpr
constinit
变量中。然后,在某种程度上,如果需要用户帮助编译器查看正确的路径,则该变量会忽略两种情况下都有一个函数的值

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

    constexpr auto temp = foo(6); // foo_compiletime
    auto t4 = temp;
但是,我们再次要求用户注意foo的内部行为,这并不是我们想要实现的

见悲观情绪


有关这方面的更多信息,请参见。

gcc有
\u内置常量\u p()
,@user643722抱歉,我的评论缺少“或”。有
    auto t4 = foo_compiletime(6);
    constexpr auto temp = foo(6); // foo_compiletime
    auto t4 = temp;