Detecting constexpr with SFINAE

NOTE: I opened a question here about whether OPs code is actually valid. My rewritten example below will work in any case.


but I would like to know if the code is legal C++11

It is, although the default template argument may be considered a bit unusual. I personally like the following style better, which is similar to how you (read: I) write a trait to check for a function's existence, just using a non-type template parameter and leaving out the decltype:

#include <type_traits>

namespace detail{
template<int> struct sfinae_true : std::true_type{};
template<class T>
sfinae_true<(T::f(), 0)> check(int);
template<class>
std::false_type check(...);
} // detail::

template<class T>
struct has_constexpr_f : decltype(detail::check<T>(0)){};

Live example.


Explanation time~

Your original code works because a default template argument's point of instantiation is the point of instantiation of its function template, meaning, in your case, in main, so it can't be substituted earlier than that.

§14.6.4.1 [temp.point] p2

If a function template [...] is called in a way which uses the definition of a default argument of that function template [...], the point of instantiation of the default argument is the point of instantiation of the function template [...].

After that, it's just usual SFINAE rules.


† Atleast I think so, it's not entirely clear in the standard.


Prompted by @marshall-clow, I put together a somewhat more-generic version of an type-trait for detecting constexpr. I modelled it on std::invoke_result, but because constexpr depends on the inputs, the template arguments are for the values passed in, rather than the types.

It's somewhat limited, as the template args can only be a limited set of types, and they're all const when they get to the method call. You can easily test a constexpr wrapper method if you need other types, or non-const lvalues for a reference parameter.

So somewhat more of an exercise and demonstration than actually-useful code.

And the use of template<auto F, auto... Args> makes it C++17-only, needing gcc 7 or clang 4. MSVC 14.10.25017 can't compile it.

namespace constexpr_traits {

namespace detail {

// Call the provided method with the provided args.
// This gives us a non-type template parameter for void-returning F.
// This wouldn't be needed if "auto = F(Args...)" was a valid template
// parameter for void-returning F.
template<auto F, auto... Args>
constexpr void* constexpr_caller() {
    F(Args...);
    return nullptr;
}

// Takes a parameter with elipsis conversion, so will never be selected
// when another viable overload is present
template<auto F, auto... Args>
constexpr bool is_constexpr(...) { return false; }

// Fails substitution if constexpr_caller<F, Args...>() can't be
// called in constexpr context
template<auto F, auto... Args, auto = constexpr_caller<F, Args...>()>
constexpr bool is_constexpr(int) { return true; }

}

template<auto F, auto... Args>
struct invoke_constexpr : std::bool_constant<detail::is_constexpr<F, Args...>(0)> {};

}

Live demo with use-cases on wandbox