是否可以测试constexpr函数是否在编译时求值
Is it possible to test if a constexpr function is evaluated at compile time?
由于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)
内部知道函数是在编译时还是在运行时执行的?
编辑:还有可能在运行时知道函数是否在编译时求值吗?
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,从而使测试变得无用。
列出的技术有效,但由于它使用static_assert
,因此对sfinae不友好。一个更好的方法(理论上,你会明白我的意思)是检查一个函数是否是noexcept
。为什么?因为,常量表达式总是noexcept,即使函数没有标记为noexcept。因此,考虑以下代码:
template <class T>
constexpr void test_helper(T&&) {}
#define IS_CONSTEXPR(...) noexcept(test_helper(__VA_ARGS__))
test_helper
是constexpr
,所以只要它的参数是,它就会是一个常量表达式。如果它是常量表达式,它将是noexcept
,但否则它就不会是(因为它没有标记为这样)。
现在让我们定义一下:
double bar(double x) { return x; }
constexpr double foo(double x, bool b) {
if (b) return x;
else return bar(x);
}
如果x
是常数表达式,并且b
为真,则foo
仅为noexcept
;如果布尔值为false,那么我们调用一个非constexpr
函数,破坏我们的constexpr。所以,让我们测试一下:
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有一个多年的错误,不能正确处理这个问题。然而,gcc确实如此。现场示例:http://coliru.stacked-crooked.com/a/e7b037932c358149.它应该打印"100"。
在constexpr
函数中,您无法判断是否在c++20之前的constexpr
上下文中对您进行求值。从c++20开始,添加了这个函数——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
如果bar(foo, true)
不能在编译时求值,则CONSTEXPR_EVAL( bar(foo, true) )
将无法编译,并且如果可以在编译时对其求值,则返回该值。
其他涉及noexcept
(编译时评估的函数为noexcept
)的技巧也可以奏效(参见@NirFriedman的回答)。
我认为实现这一点的规范方法是使用static_assert
。static_assert
在编译时进行求值,因此如果条件为false,它们将破坏构建。
#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
对我来说编译得很好,表明一切都按预期进行。
如果您可以使用C++20,那么std::is_constant_evaluated
正是您想要的。CCD_ 36通常使用内部编译器来实现。
这在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+),因此您可能还需要检测编译器版本。
基于本次讨论中的信息,我精心制作了以下最小示例:
template <class T>
constexpr void test_helper(T &&) {}
#define IS_CONSTEXPR(...) noexcept(test_helper(__VA_ARGS__))
constexpr void test(){
static_assert(IS_CONSTEXPR(10), "asdfadsf");
constexpr const int x = 10;
static_assert(IS_CONSTEXPR(x), "asdfadsf");
int y;
static_assert(IS_CONSTEXPR(y), "asdfadsf");
}
int main(){
test();
return 0;
}
令我失望的是,它未能在每个static_assels上进行编译。看见https://www.godbolt.org/z/Tr3z93M3s
很抱歉破坏了聚会,但肯定没有标准的方法。在假设规则下,编译器可以发出在运行时计算结果的代码,即使在这种情况下,编译器已经被迫在不同的上下文中在编译时计算结果。任何可以在编译时完成的操作都可以在运行时再次完成,对吧?而且这个计算已经被证明是不会抛出的。
因此,通过扩展,任何符合标准的IS_REALLY_CONSTEXPR
或is_really_constexpr
检查都不能证明完全相同的调用,或者完全相同的constexpr
符号的值涉及运行时计算。
当然,通常没有任何理由在运行时重复可以在编译时完成甚至已经完成的计算,但问题是要判断编译器是否使用了预先计算的结果,而且没有。
现在,您确实说过可能是标准,所以实际上,您最好的选择可能是用您选择的编译器测试所提供的解决方案之一,并希望它的行为一致。(或者阅读源代码,如果它是开放/公共源代码,并且你很喜欢。)
- 是否可以在编译时初始化数组,以便在运行时不会花费时间?
- 关于std::move的使用,是否有编译警告
- 标准是否严格定义了该程序应该如何编译?
- 是否可以在编译时限制类的实例数?
- 检查编译时是否存在静态函数
- 是否可以编译 Gtk+ 应用程序以同时在 Gtk 2 和 3 上运行
- std::less是否应该允许在编译时比较不相关的指针?
- 编译时检查特征专用化是否具有唯一 ID
- GTK C++:找不到信号处理程序 您是否使用 -rdynamic 进行了编译?
- 是否可以在编译时初始化对象的 C 样式函数指针,以便它调用对象的成员函数?
- 如何确定integer_sequence在编译时是否包含给定的数字?
- 知道模板参数在编译时是否为 const char*?
- 全局变量在 C++ 中是否显着提高了编译速度?
- 检查该类在编译时C++中是否有任何基类
- 如何判断是否在编译时计算了"constexpr"(无需手动检查)
- cMake/cTest:检查代码是否编译
- 我怎么知道编译器是否C++编译时计算表达式
- 在线编译器工具是否执行所有操作,或者它们只是检查是否编译
- constexpr数组成员是否编译时间常量
- 检查表达式是否编译的可移植方法