How Does std::enable_if work?

Jonathan Mee picture Jonathan Mee · Aug 13, 2014 · Viewed 9.7k times · Source

I just asked this question: std::numeric_limits as a Condition

I understand the usage where std::enable_if will define the return type of a method conditionally causing the method to fail to compile.

template<typename T>
typename std::enable_if<std::numeric_limits<T>::is_integer, void>::type foo(const T &bar) { isInt(bar); }

What I don't understand is the second argument and the seemingly meaningless assignment to std::enable_if when it's declared as part of the template statement, as in Rapptz answer.

template<typename T, typename std::enable_if<std::is_integral<T>::value, int>::type = 0>
void foo(const T& bar) { isInt(); }

Answer

Jonathan Mee picture Jonathan Mee · Aug 18, 2014

As is mentioned in comment by 40two, understanding of Substitution Failure Is Not An Error is a prerequisite for understanding std::enable_if.

std::enable_if is a specialized template defined as:

template<bool Cond, class T = void> struct enable_if {};
template<class T> struct enable_if<true, T> { typedef T type; };

The key here is in the fact that typedef T type is only defined when bool Cond is true.

Now armed with that understanding of std::enable_if it's clear that void foo(const T &bar) { isInt(bar); } is defined by:

template<typename T>
typename std::enable_if<std::numeric_limits<T>::is_integer, void>::type foo(const T &bar) { isInt(bar); }

As mentioned in firda's answer, the = 0 is a defaulting of the second template parameter. The reason for the defaulting in template<typename T, typename std::enable_if<std::is_integral<T>::value, int>::type = 0> is so that both options can be called with foo< int >( 1 );. If the std::enable_if template parameter was not defaulted, calling foo would require two template parameters, not just the int.


General note, this answer is made clearer by explicitly typing out typename std::enable_if<std::numeric_limits<T>::is_integer, void>::type but void is the default second parameter to std::enable_if, and if you have enable_if_t is a defined type and should be used. So the return type should condense to: std::enable_if_t<std::numeric_limits<T>::is_integer>

A special note for users of prior to : Default template parameters aren't supported, so you'll only be able to use the enable_if on the function return: std::numeric_limits as a Condition