Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Compile-time or runtime detection within a constexpr function

Tags:

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.

like image 823
Kumputer Avatar asked Nov 03 '16 19:11

Kumputer


1 Answers

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__) 
like image 167
Aaron McDaid Avatar answered Sep 18 '22 05:09

Aaron McDaid