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