constexpr函数中的编译时或运行时检测
Compile-time or runtime detection within a constexpr function
当constexpr在c++ 11中引入时,我很兴奋,但不幸的是,我对它的有用性做出了乐观的假设。我假设我们可以在任何地方使用constexpr来捕获文字编译时常量或文字编译时常量的任何constexpr结果,包括如下内容:
constexpr float MyMin(constexpr float a, constexpr float b) { return a<b?a:b; }
因为将函数的返回类型限定为constexpr并不限制其在编译时的使用,并且还必须在运行时可调用,我认为这将是一种确保MyMin只能与编译时求值的常量一起使用的方法,这将确保编译器永远不会允许它在运行时执行,从而使我可以编写另一个更适合运行时的MyMin版本,理想情况下使用与_mm_min_ss相同的名称。确保编译器不会生成运行时分支代码。不幸的是,函数参数不能被constexpr,所以这似乎是不可能的,除非像这样:
constexpr float MyMin(float a, float b)
{
#if __IS_COMPILE_TIME__
return a<b?a:b;
#else
return _mm_cvtss_f32(_mm_min_ss(_mm_set_ss(a),_mm_set_ss(b)));
#endif
}
我非常怀疑msvc++是否有这样的功能,但我希望GCC或clang至少有一些功能来完成它,无论它看起来多么不优雅。
当然,我给出的例子非常简单,但是如果您可以发挥您的想象力,在许多情况下,您可以自由地做一些事情,例如在您知道只能在编译时执行的函数中广泛使用分支语句,因为如果它在运行时执行,性能将受到损害。
可以检测给定的函数调用表达式是否为常量表达式,从而在两种不同的实现之间进行选择。下面使用的泛型lambda需要c++ 14。
(这个答案来自@Yakk对我去年问过的一个问题的回答)。
我不确定我在多大程度上推动了标准。这在clang 3.9上进行了测试,但会导致g++ 6.2给出"内部编译器错误"。我将在下周发送一个bug报告(如果没有其他人先做的话!)
第一步是将constexpr
实现作为constexpr static
方法移动到struct
中。更简单地说,您可以让当前的constexpr
保持原样,并从新的struct
的constexpr static
方法调用它。
struct StaticStruct {
static constexpr float MyMin_constexpr (float a, float b) {
return a<b?a:b;
}
};
同样,定义这个(即使它看起来没用!):
template<int>
using Void = void;
基本思想是Void<i>
要求i
是一个常量表达式。更准确地说,以下lambda仅在某些情况下才具有合适的重载:
auto l = [](auto ty)-> Void<(decltype(ty):: MyMin_constexpr(1,3) ,0)>{};
------------------/
testing if this
expression is a
constant expression.
只有当实参ty
是类型为StaticStruct
和且感兴趣的表达式(MyMin_constexpr(1,3)
)是常量表达式时,才能调用l
。如果我们用非常量参数替换1
或3
,那么泛型lambda l
将通过SFINAE失去该方法。
因此,以下两个测试是等价的:
StaticStruct::MyMin_constexpr(1,3)
是常量表达式吗?l
可以通过l(StaticStruct{})
调用吗?
很容易从上面的lambda中删除auto ty
和decltype(ty)
。但这将导致一个严重的错误(在非常数情况下),而不是一个漂亮的替换失败。因此,我们使用auto ty
来获得替换失败(我们可以有效地检测)而不是错误。
下一段代码返回std:true_type
很简单当且仅当f
(我们的通用lambda)可以被a
(StaticStruct
)调用:
template<typename F,typename A>
constexpr
auto
is_a_constant_expression(F&& f, A&& a)
-> decltype( ( std::forward<F>(f)(std::forward<A>(a)) , std::true_type{} ) )
{ return {}; }
constexpr
std::false_type is_a_constant_expression(...)
{ return {}; }
接下来,演示它的用法:
int main() {
{
auto should_be_true = is_a_constant_expression(
[](auto ty)-> Void<(decltype(ty):: MyMin_constexpr(1,3) ,0)>{}
, StaticStruct{});
static_assert( should_be_true ,"");
}
{
float f = 3; // non-constexpr
auto should_be_false = is_a_constant_expression(
[](auto ty)-> Void<(decltype(ty):: MyMin_constexpr(1,f) ,0)>{}
, StaticStruct{});
static_assert(!should_be_false ,"");
}
}
为了直接解决你最初的问题,我们可以首先定义一个宏来避免重复:
(我还没有测试这个宏,为任何拼写错误道歉。)
#define IS_A_CONSTANT_EXPRESSION( EXPR )
is_a_constant_expression(
[](auto ty)-> Void<(decltype(ty)::
EXPR ,0)>{}
, StaticStruct{})
在这个阶段,也许您可以简单地执行:
#define MY_MIN(...)
IS_A_CONSTANT_EXPRESSION( MyMin_constexpr(__VA_ARGS__) ) ?
StaticStruct :: MyMin_constexpr( __VA_ARGS__ ) :
MyMin_runtime ( __VA_ARGS__ )
或者,如果您不相信您的编译器可以通过?:
来优化std::true_type
和std::false_type
,那么可能:
constexpr
float MyMin(std::true_type, float a, float b) { // called if it is a constant expression
return StaticStruct:: MyMin_constexpr(a,b);
}
float MyMin(std::false_type, float , float ) { // called if NOT a constant expression
return MyMin_runtime(a,b);
}
用宏代替:
#define MY_MIN(...)
MyMin( IS_A_CONSTANT_EXPRESSION(MyMin_constexpr(__VA_ARGS__))
, __VA_ARGS__)
我认为这将是一种确保MyMin只能用于编译时求值常量的方法,这将确保编译器永远不会允许它在运行时执行
是的;有办法的。
也可以在c++ 11中使用。
谷歌我发现了一种奇怪的中毒方式(作者:Scott Schurr):简而言之,如下
extern int no_symbol;
constexpr float MyMin (float a, float b)
{
return a != a ? throw (no_symbol)
: (a < b ? a : b) ;
}
int main()
{
constexpr float m0 { MyMin(2.0f, 3.0f) }; // OK
float f1 { 2.0f };
float m1 { MyMin(f1, 3.0f) }; // linker error: undefined "no_symbol"
}
如果我理解得好,它背后的想法是,如果MyMin()
在编译时执行,throw(no_symbol)
永远不会被使用(a != a
永远是假的),所以没有必要使用被声明为extern
但从未定义的no_symbol
(throw()
不能在编译时使用)。
如果您使用MyMin()
运行时,throw(no_symbol)
被编译,no_symbol
在链接阶段给出错误。
更一般地说,有一个建议(来自Scott Schurr),但我不知道实现。
—EDIT—
正如T.C.(谢谢!)所指出的那样,这个解决方案工作(如果工作和何时工作)只是因为编译器在这一点上没有优化以理解a != a
是假的。
特别地,MyMin()
工作(没有良好的优化),因为在这个例子中,我们使用浮点数,如果a
是NaN, a != a
可以为真,所以编译器更难检测到throw()
部分无用。如果MyMin()
是一个整数函数,则可以将函数体写成
float(a) != float(a)
来阻止编译器的优化)constexpr int MyMin (int a, int b)
{
return float(a) != float(a) ? throw (no_symbol)
: (a < b ? a : b) ;
}
但是对于没有"自然的"可抛出错误情况的函数来说并不是一个真正的解决方案。
当它是一个自然错误的情况下,应该给出错误(编译或运行),这是不同的:编译器不能优化和技巧工作。
示例:如果MyMin()
返回a
和b
之间的最小值,但a
和b
是不同的,或者MyMin()
应该给出编译错误(不是一个很好的例子…我知道),所以
constexpr float MyMin (float a, float b)
{
return a != b ? throw (no_symbol)
: (a < b ? a : b) ;
}
工作,因为编译器不能优化a != b
,必须编译(给出链接器错误)throw()
部分。
- 如何在运行时之前检测logic_error异常?
- 如何在 c++ 运行时检测当前操作系统?
- 以编程方式在运行时检测 CPU 体系结构
- 检测到"运行时库"的不匹配LNK2038:值"MT_StaticRelease"与 xxx.obj 中的值"MTd_StaticDebug"不匹配
- 如果在C 程序中使用OpenMP,请在运行时检测
- 寻找一种在运行时检测 valgrind/memcheck 的方法,而无需包含 valgrind 标头
- 在使用高架特权运行时,如何正确检测网络驱动器
- Java 运行时环境检测到致命错误:Java 中的 OpenCV 椭圆检测
- 如何在运行时检测 C 中的操作系统
- 如何检测程序运行时 Kinect 何时断开连接/拔出
- 使用Visual Studio在OpenCV中运行人脸检测时出现错误LNK2019
- 检测并更正导致运行时错误的代码
- 运行时边界数组的源级别检测有多困难
- constexpr函数中的编译时或运行时检测
- 如何在我的机器上用c++在运行时检测临时端口范围?
- 在运行时检测端序的好处是什么?
- 如何在编译或运行时检测非虚拟覆盖
- 内存删除的运行时检测
- 在运行时检测应用程序的所有依赖项
- 在运行时检测和拦截链接库依赖项