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.
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 vial(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__)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With