在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 ,并从一个新structconstexpr 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.

只有当参数tyStaticStruct类型,并且我们感兴趣的表达式( MyMin_constexpr(1,3) )是一个常量表达式时,我们才可以调用l 。 如果我们用非常量参数替换13 ,那么通用lambda l将通过SFINAE失去该方法。

因此,以下两个测试是等同的:

  • StaticStruct::MyMin_constexpr(1,3)一个常量表达式吗?
  • 可以l通过被称为l(StaticStruct{})
  • 简单地从上面的lambda中删除auto tydecltype(ty)是很诱人的。 但是这会给出一个硬错误(在非常量情况下)而不是一个很好的替换失败。 因此,我们使用auto ty来获取替换失败(我们可以有效检测)而不是错误。

    接下来的这个代码是返回一个简单的事情std:true_type当且仅当f (我们的通用拉姆达)可以被称为aStaticStruct ):

    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_typestd::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()返回ab之间a最小值,但ab将是不同的,或者MyMin()应该给出编译器错误(这不是一个很好的例子......我知道),所以

    constexpr float MyMin (float a, float b)
     {
       return a != b ? throw (no_symbol)
                     : (a < b ? a : b) ;
     }
    

    因为编译器无法优化a != b并且必须编译(给出链接器错误) throw()部分。

    链接地址: http://www.djcxy.com/p/68367.html

    上一篇: time or runtime detection within a constexpr function

    下一篇: Why constexpr is not evaluated at compile time (MSVC 2015)?