Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
172 views
in Technique[技术] by (71.8m points)

c++ - Compile-time or runtime detection within a constexpr function

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 catch literal compile-time constants or any constexpr result of a literal compile-time constant, including something like this:

constexpr float MyMin(constexpr float a, constexpr float b) { return a<b?a:b; }

Because qualifying a function's return type only as constexpr does not limit its usage to compile-time, and must also be callable at runtime, 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, freeing me to write an alternative more runtime friendly version of MyMin, ideally with the same name that uses a _mm_min_ss intrinsic, ensuring that the compiler won't generate runtime branching code. Unfortunately, function parameters cannot be constexpr, so it would seem that this cannot be done, unless something like this were possible:

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
}

I have serious doubts that MSVC++ has anything like this at all, but I was hoping maybe GCC or clang have at least something to accomplish it, however unelegant it may look like.

Granted, the example I presented was very simplistic, but if you can use your imagination, there are many cases where you could feel free to do something like make extensive use of branching statements within a function that you know can only execute at compile time, because if it executed at runtime, performance would be compromised.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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__)

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...