Compile-time or runtime detection within a constexpr function

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

Leave a Comment