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