在constexpr函数中进行时间或运行时检测
当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发展到去年我问过的一个问题)。
我不知道我推多少标准。 这是在3.9版中测试的,但会导致g ++ 6.2发出“内部编译器错误”。 我将在下周发送一份错误报告(如果没有其他人先做它!)
第一步是将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
(我们的通用拉姆达)可以被称为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__) ) ?
Static_Struct :: 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
永远是false),所以不需要使用声明为extern
no_symbol
,但是从不定义(并且throw()
不能用于编译时)。
如果使用MyMin()
运行时,则会编译throw(no_symbol)
,并且no_symbol
会在链接阶段发生错误。
更一般地说,有一个提案(曾经来自Scott Schurr),但我没有意识到实现。
---编辑---
正如TC所指出的那样(谢谢!)这个解决方案的工作原理(如果工作和工作的时候)只是因为编译器没有在理解a != a
是假的时候不进行优化。
特别是, MyMin()
可以工作(没有很好的优化),因为在这个例子中,我们使用浮点数,如果a
是NaN,那么a != a
就可以是真的,所以编译器检测throw()
部分是无用的。 如果MyMin()
是整数函数,则可以写入正文(使用测试float(a) != float(a)
试图阻止compliler优化)
constexpr int MyMin (int a, int b)
{
return float(a) != float(a) ? throw (no_symbol)
: (a < b ? a : b) ;
}
但对于不存在“自然”抛出错误情况的函数来说,它并不是真正的解决方案。
当它是一个应该给出错误(编译或运行)的自然错误情况时,情况就不一样了:编译器无法优化,而且这个技巧是可行的。
例如:如果MyMin()
返回a
和b
之间a
最小值,但a
和b
将是不同的,或者MyMin()
应该给出编译器错误(这不是一个很好的例子......我知道),所以
constexpr float MyMin (float a, float b)
{
return a != b ? throw (no_symbol)
: (a < b ? a : b) ;
}
因为编译器无法优化a != b
并且必须编译(给出链接器错误) throw()
部分。
上一篇: time or runtime detection within a constexpr function
下一篇: Why constexpr is not evaluated at compile time (MSVC 2015)?