30

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(); }
Community
  • 1
  • 1
Jonathan Mee
  • 37,899
  • 23
  • 129
  • 288
  • 13
    (You are missing a `typename` in the second block of code you quoted.) `typename std::enable_if::value, int>::type` is `int` if the value is `true`, so it declares a non-type template parameter of type `int` with a default value of `0`; if value is `false`, you get substitution failure so the overload is removed from consideration. – T.C. Aug 13 '14 at 11:09
  • 4
    In order to understand how `std::enable_if` works you'll have to understand how [**SFINAE**](http://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/SFINAE) works. – 101010 Aug 13 '14 at 11:34
  • @T.C. so it's trying to do an assignment at compile time and *if* it is assigning to an `int` the assignment succeeds? – Jonathan Mee Aug 13 '14 at 11:34
  • @JonathanMee [It's not assignment.](http://en.cppreference.com/w/cpp/language/template_parameters#Default_template_arguments) –  Aug 13 '14 at 11:36
  • 2
    Here is good explanation of enable_if, http://www.boost.org/doc/libs/1_55_0/libs/utility/enable_if.html. It covers boost::enable_if, but std::enable_if works the same way. – Johan Råde Aug 13 '14 at 11:40
  • 2
    @user763305 I've read through the boost documentation and it really helped me understand what's going down here. I was planning on writing a synopsis of it for an answer here as time permits. I will accept your answer instead though if you feel like writing a synopsis first. – Jonathan Mee Aug 15 '14 at 12:08
  • I'm glad you found my comment useful. I don't have time to write a full answer; please go ahead and do it yourself. – Johan Råde Aug 15 '14 at 12:44
  • @user763305 I've gone ahead and scribbled up an answer, I'll accept it in a couple days, if you think it could use clarification, please feel free to edit it. – Jonathan Mee Aug 18 '14 at 12:41
  • @T.C. Why, in the implementation of std::enable_if, the template parameter T is defaulted to void? Isn't it the same without? – gedamial Jul 18 '17 at 21:17
  • @gedamial Yes in the case where the type returned will be `void`, the second template argument to `enable_if` may be omitted. The case I asked a question about illustrated passing a integral value, not a type, as a template argument. And defaulting that argument to 0. So given the function definition: `template::value, int>::type = 0> void foo(const T& bar) { isInt(); }` I could call this function like: `foo(1)` or `foo(1)` in the latter case the second template argument would be defaulted to 0. – Jonathan Mee Jul 18 '17 at 22:01
  • @JonathanMee thank for the answer. then why is it important, in the implementation of std::enable_if, that the parameter T is defaulted to void (in the case the bool Condition is false)? If the condition is false, the function will be just hidden... there's no need to write `typename T = void` – gedamial Jul 18 '17 at 22:04
  • @gedamial If you're intensely curious as to the motivation for defaulting it might warrant a follow up question. As it is though it does let us write things a bit more simply. For example I can rewrite my first example in this question to something as simple as: `template enable_if_t> foo(const T& bar) { isInt(bar); }` If you do ask the question though, add a link here, if there were any good motivation rationales I'd like to read them. – Jonathan Mee Jul 18 '17 at 23:06

2 Answers2

31

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

Jonathan Mee
  • 37,899
  • 23
  • 129
  • 288
10
template<typename T, std::enable_if<std::is_integral<T>::value, int>::type = 0>
void foo(const T& bar) { isInt(); }

this fails to compile if T is not integral (because enable_if<...>::type won't be defined). It is protection of the function foo.The assignment = 0 is there for default template parameter to hide it.

Another possibility: (yes the typename is missing in original question)

#include <type_traits>

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

template<typename T>
typename std::enable_if<std::is_integral<T>::value>::type
bar(const T& foo) {}

int main() {
    foo(1); bar(1);
    foo("bad"); bar("bad");
}
error: no matching function for call to ‘foo(const char [4])’
  foo("bad"); bar("bad");
           ^
note: candidate is:
note: template::value, int>::type  > void foo(const T&)
 void foo(const T& bar) {}
      ^
note:   template argument deduction/substitution failed:
error: no type named ‘type’ in ‘struct std::enable_if’
 template::value, int>::type = 0>
                                                                                       ^
note: invalid template non-type parameter
error: no matching function for call to ‘bar(const char [4])’
  foo("bad"); bar("bad");
                       ^
firda
  • 3,268
  • 17
  • 30