Before the introduction of concepts and constraints, there are several ways to simulate this compile-time check. Take a "order()
" function for example: (how to implement LessThanComparable
without concepts or constraints is another story)
Use
static_assert
template <typename T, typename U> void order(T& a, U& b) { static_assert(LessThanComparable<U,T>, "oh this is not epic"); if (b < a) { using std::swap; swap(a, b); } }
This approach won't work for function overloading.
Use
typename = enable_if
template <typename T, typename U, typename = std::enable_if_t<LessThanComparable<U,T>>>> void order(T& a, U& b) { if (b < a) { using std::swap; swap(a, b); } }
What if an over-"intelligent" guy specifies a third parameter by hand?
Use
enable_if
in the function prototype:template <typename T, typename U> std::enable_if_t<LessThanComparable<U,T>>, void> order(T& a, U& b) { if (b < a) { using std::swap; swap(a, b); } }
Sometimes also doesn't work in function overloading.
Use
enable_if
as the type of a dummy non-type template parametertemplate <typename T, typename U, std::enable_if_t<LessThanComparable<U,T>>, void*> = nullptr> // or int = 0 void order(T& a, U& b) { if (b < a) { using std::swap; swap(a, b); } }
I saw this before, and I can't think of any drawbacks.
And many other variants.
Which ones are preferable or recommended? What is the benefits and drawbacks? Any help is appreciated.
You should look how
range-v3
library emulates concepts https://github.com/ericniebler/range-v3/blob/master/include/range/v3/range_concepts.hppThere is also a way to use template aliases to achieve something similar to concepts Using alias templates for sfinae: does the language allow it?
And, you have missed
decltype
variants on your list:It's a complicated topic and is not easy give an answer at your question.
Anyway, some observations/suggestions, without any pretense to being exhaustive.
(1) the
static_assert()
wayis a good solution if you want a function that works only with some types and give an error (a clear error, because you can choose the error message) if called with wrong types.
But usually is the wrong solution when you want an alternative. Isn't a SFINAE solution. So calling the function with arguments of wrong types gives an error and doesn't permit that another function is used in substitution.
(2) You're right about the
solution. An user can explicit a third parameter by hand. Joking, I say that this solution can be "hijacked".
But isn't the only drawback of this solution.
Suppose you have two complementary
foo()
functions that have to be enabled/disabled via SFINAE; the first one when a test is true, the second one when the same test if false.You can think that the following solution is dangerous (can be hijacked) but can work
Wrong: this solution simply doesn't works because you're enabling/disabling not the second typename but only the default value for the second typename. So you're not completely enabling/disabling the functions and the compiler have to consider two functions with the same signature (the signature of a function doesn't depends from default values); so you have a collision and obtain an error.
The following solutions, SFINAE over the returned type
(also without
void
, that is the default type) or over the second type (following the Yakk's suggestion about a not-standard allowed
void *
)are (IMHO) good solutions because both of they avoid the hijacking risk and are compatible with two complementary functions with the same name and signature.
I suggest a third possible solution (no hijack-able, complementary compatible) that is the addition of a third defaulted value with SFINAE enabled/disabled type: something as
Another possible solution avoid at all SFINAE but use tag-dispatching; something as
This in case
LessThanComplarable
inherit fromstd::true_type
when the condition is true, fromstd::false_type
when the condition is false.Otherwise, if
LessThanComparable
gives only a boolean value, the call toorder_helper()
can be(3) if you can use C++17, there is the
if constexpr
way that can avoid a lot of overloadingNon-type template parameters of type
void*
are not allowed in at least some versions of the standard; I'd usebool
with value=true
.Otherwise, use that.