I'm working on upgrading some C++ code to take advantage of the new functionality in C++11. I have a trait class with a few functions returning fundamental types which would most of the time, but not always, return a constant expression. I would like to do different things based on whether the function is constexpr
or not. I came up with the following approach:
template<typename Trait> struct test { template<int Value = Trait::f()> static std::true_type do_call(int){ return std::true_type(); } static std::false_type do_call(...){ return std::false_type(); } static bool call(){ return do_call(0); } }; struct trait { static int f(){ return 15; } }; struct ctrait { static constexpr int f(){ return 20; } }; int main() { std::cout << "regular: " << test<trait>::call() << std::endl; std::cout << "constexpr: " << test<ctrait>::call() << std::endl; }
The extra int
/...
parameter is there so that if both functions are available after SFINAE, the first one gets chosen by overloading resolution.
Compiling and running this with Clang 3.2 shows:
regular: 0 constexpr: 1
So this appears to work, but I would like to know if the code is legal C++11. Specially since it's my understanding that the rules for SFINAE have changed.
The easiest way to check whether a function (e.g., foo ) is constexpr is to assign its return value to a constexpr as below: constexpr auto i = foo(); if the returned value is not constexpr compilation will fail.
Quick A: constexpr guarantees compile-time evaluation is possible if operating on a compile-time value, and that compile-time evaluation will happen if a compile-time result is needed.
constexpr functions will be evaluated at compile time when all its arguments are constant expressions and the result is used in a constant expression as well.
Yes. I believe putting such const ness is always a good practice wherever you can. For example in your class if a given method is not modifying any member then you always tend to put a const keyword in the end.
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
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