In C++03 you simply write enable_if
yourself. It requires no C++11 features.
The reason why you use different techniques is that the pre-C++11 compilers sometimes have a funny definition of what SFINAE and what should be an error. MSVC is the current major compiler who still (in the pre-C++17 era) has very quirky definition of what is valid SFINAE due to their "SFINAE decltype" issues.
In C++11 you should write void_t
and enable_if_t
to simplify your SFINAE stuff.
You should also write this:
namespace details {
template<template<class...>class Z, class always_void, class...Ts>
struct can_apply:std::false_type{};
template<template<class...>class Z, class...Ts>
struct can_apply<Z, void_t<Z<Ts...>>, Ts...>:std::true_type{};
}
template<template<class...>class Z, class...Ts>
using can_apply = details::can_apply<Z, void, Ts...>;
which lets you write traits and ask if something is valid easily (can you call a method? Create an alias that does a decltype
on the invocation, then ask if you can apply the type to the alias). This is still needed in C++14 and 17, but C++20 probably will get is_detected
which serves a similar purpose.
So can_print
is:
template<class T>using print_result = decltype(
std::declval<std::ostream&>() << std::declval<T>()
);
template<class T>using can_print = can_apply< print_result, T >;
it is either truthy or falsy depending on if <<
works on a stream with it.
In C++14 you can start using hana-style metaprogramming to create lambdas that do type manipulation for you. In C++17 they become constexpr
, which gets rid of some issues.
Using techniques like the OP's macro tends to lead to ill formed programs, no diagnostic required. This is because if a template has no valid template parameters that would lead to the body of the template being valid code, your program is ill formed, no diagnostic required.
So this:
template<REQUIRES(sizeof(int)==4)>
int fun() {
// code that is ill-formed if `int` does not have size 4
}
will most likely compile and run and "do what you want", but it is actually an ill-formed program when sizeof(int)
is 8
.
The same may be true of using this technique to disable methods on classes based on the template arguments of the class. The stanard is unclear on the issue, so I avoid it.
The REQUIRES
macro tries to hide how it works behind magic, but it is far too easy to step over the line and generate an ill-formed program. Hiding the magic when the details of the magic cause your code to be ill-formed is not a good plan.
Tag dispatching can be used to simplify otherwise complex SFINAE issues. It can be used to order overloads or pick between them, or pass more than one bundle of types to a helper template function.
template<std::size_t N>
struct overload_priority : overload_priority<N-1> {};
template<>
struct overload_priority<0> {};
Now you can pass overload_priority<50>{}
to a set of functions, and the one with the highest overload_priority<?>
in that slot will be preferred.
template<class T>struct tag_t{using type=T;};
namespace details {
inline int fun( tag_t<int[4]> ) { return 0; }
inline int fun( tag_t<int[8]> ) { return 1; }
}
int fun() { return details::fun( tag_t<int[sizeof(int)]>{} ); }
just dispatched to a different function depending on the size of int
.
Both fun
overloads get compiled and checked, so you don't run into the stealth ill-formed program problem.
A function whose validity is not a function of its template arguments is not safe to use in C++. You must use a different tactic. Machinery that makes this easier to do just makes it easier to write ill-formed programs.