Compile-time or runtime detection within a constexpr function

前端 未结 2 1122
没有蜡笔的小新
没有蜡笔的小新 2020-12-01 06:46

I was excited when constexpr was introduced in C++11, but I unfortunately made optimistic assumptions about its usefulness. I assumed that we could use constexpr anywhere to

相关标签:
2条回答
  • 2020-12-01 07:25

    I figured that this would be a way to ensure that MyMin can only ever be used with compile-time evaluated constants, and this would ensure that the compiler would never allow its execution at runtime

    Yes; there is a way.

    And works with C++11 too.

    Google-ing I've found a strange poisoning way (by Scott Schurr): in short, the following

    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"
     }
    

    If I understand well, the idea behind it is that if MyMin() is executed compile time, throw(no_symbol) is never used (a != a is ever false) so there is no need to use no_symbol that is declared extern but is never defined (and throw() can't be used compile time).

    If you use MyMin() run-time, throw(no_symbol) is compiled and no_symbol gives an error in linking phase.

    More generally speaking, there is a proposal (ever from Scott Schurr) but I'm not aware of implementations.

    --- EDIT ---

    As pointed by T.C. (thanks!) this solution work (if works and when works) only because the compiler doesn't optimize at point to understand that a != a is ever false.

    In particular, MyMin() works (without good optimizations) because, in the example, we're working with float numbers and a != a can be true if a is NaN, so it's more difficult for a compiler detect that the throw() part is unuseful. If MyMin() is a function for integers, the body can be written (with test float(a) != float(a) to try to obstruct the compliler optimizations) as

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

    but isn't a real solution for a function where there ins't a "natural" throw-able error case.

    When it's a natural error case that should give error (compiling or running), it's different: the compiler can't optimize and the trick work.

    Example: if MyMin() return the minimun value between a and b but a and b are to be differents or MyMin() should give a compiler error (not a great example... I know), so

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

    works because the compiler can't optimize a != b and must compile (giving linker error) the throw() part.

    0 讨论(0)
  • 2020-12-01 07:30

    It is possible to detect if a given function-call expression is a constant expression, and thereby select between two different implementations. Requires C++14 for the generic lambda used below.

    (This answer grew out this answer from @Yakk to a question I asked last year).

    I'm not sure how far I'm pushing the Standard. This is tested on clang 3.9, but causes g++ 6.2 to give an "internal compiler error". I'll send a bug report next week (if nobody else does it first!)

    This first step is to move the constexpr implementation into a struct as a constexpr static method. More simply, you could leave the current constexpr as is and call it from a constexpr static method of a new struct.

    struct StaticStruct {
        static constexpr float MyMin_constexpr (float a, float b) {
            return a<b?a:b;
        }
    };
    

    Also, define this (even though it looks useless!):

    template<int>
    using Void = void;
    

    The basic idea is that Void<i> requires that i be a constant expression. More precisely, this following lambda will have suitable overloads only in certain circumstances:

    auto l = [](auto ty)-> Void<(decltype(ty)::   MyMin_constexpr(1,3)   ,0)>{};
                                                  \------------------/
                                                   testing if this
                                                   expression is a
                                                   constant expression.
    

    We can call l only if the argument ty is of type StaticStruct and if our expression of interest (MyMin_constexpr(1,3)) is a constant expression. If we replace 1 or 3 with non-constant arguments, then the generic lambda l will lose the method via SFINAE.

    Therefore, the following two tests are equivalent:

    • Is StaticStruct::MyMin_constexpr(1,3) a constant expression?
    • Can l be called via l(StaticStruct{})?

    It's tempting to simply delete auto ty and decltype(ty) from the above lambda. But that will give a hard error (in the non-constant case) instead of a nice substitution failure. We therefore use auto ty to get substitution failure (which we can usefully detect) instead of error.

    This next code is a straightforward thing to return std:true_type if and only if f (our generic lambda) can be called with 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 {}; }
    

    Next, a demonstration of it's use:

    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 ,"");
        }
    }
    

    To solve your original problem directly, we could first define a macro to save repetition:

    (I haven't tested this macro, apologies for any typos.)

    #define IS_A_CONSTANT_EXPRESSION( EXPR )                \
         is_a_constant_expression(                          \
             [](auto ty)-> Void<(decltype(ty)::             \
                  EXPR                         ,0)>{}       \
             , StaticStruct{})
    

    At this stage, perhaps you could simply do:

    #define MY_MIN(...)                                            \
        IS_A_CONSTANT_EXPRESSION( MyMin_constexpr(__VA_ARGS__) ) ? \
            StaticStruct :: MyMin_constexpr( __VA_ARGS__ )     :   \
                            MyMin_runtime  ( __VA_ARGS__ )
    

    or, if you don't trust your compiler to optimize std::true_type and std::false_type through ?:, then perhaps:

    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);
    }
    

    with this macro instead:

    #define MY_MIN(...)                                             \
      MyMin( IS_A_CONSTANT_EXPRESSION(MyMin_constexpr(__VA_ARGS__)) \
           , __VA_ARGS__)
    
    0 讨论(0)
提交回复
热议问题