1

This is the code:

#include <iostream>
#include <type_traits>


template <class T>
typename std::enable_if<std::is_integral<T>::value,bool>::type
  is_odd (T i) {return bool(i%2);}

// 2. the second template argument is only valid if T is an integral type:
template < class T,
           class = typename std::enable_if<std::is_integral<T>::value>::type>
bool is_even (T i) {return !bool(i%2);}


int main() {

  short int i = 1;    // code does not compile if type of i is not integral

  std::cout << std::boolalpha;
  std::cout << "i is odd: " << is_odd(i) << std::endl;
  std::cout << "i is even: " << is_even(i) << std::endl;

  return 0;
}

I am trying to learn proper usage of enable_if, i understand if it's used as a return type specifier is that: the compiler will ignore the code. Meaning, the function won't be in the binary file.

I am somehow confused if it's used in a template argument. Based from the code above, it says there that the second template argument is only valid if T is an integral type But i am confused what is the purpose of that second argument?

I removed it and changed it to:

template < class T>
bool is_even (T i) {return !bool(i%2);}

and it still works fine. Can someone clarify me what's the real purpose of it? There's no variable specifier on it as well.

Or maybe it only serves as a checker if ill do something like

template < class T,
           class B= typename std::enable_if<std::is_integral<T>::value>::type>

Allowing me to access B on my code(can be true or false) ?

Carlos Miguel Colanta
  • 2,685
  • 3
  • 31
  • 49

2 Answers2

2

You are using it with an integral type. It will only fail when used with a non-integral type. The following should fail to compile:

float f;
is_even(f);

Note that with a C++14 compiler you can write:

template <class T,
          class = std::enable_if_t<std::is_integral<T>::value>>

However, you may want to use a static_assert here, since the function probably does not make sense for non integral types, and it will give a better error message at compile time.

Another usage of additional parameters in SFINAE is to lower the preference for a given function. The template with more template arguments with be less likely to get selected than the one with less templates arguments.

Émilien Tlapale
  • 903
  • 5
  • 11
1

The purpose of the enable_if in this case is to cause a compilation error when the deduced template argument for T is not an integral type. We can see from cppreference/is_integral that integral types are integers, characters and their signed and unsigned variants.

For any other type you will get an error that looks like:

main.cpp:21:32: error: no matching function for call to 'is_odd'
  std::cout << "i is odd: " << is_odd(NotIntegral()) << std::endl;
                               ^~~~~~
main.cpp:6:25: note: candidate template ignored: disabled by 'enable_if' [with T = NotIntegral]
typename std::enable_if<std::is_integral<T>::value,bool>::type

I understand if it's used as a return type specifier is that the compiler will ignore the code

This isn't true. The return type is evaluated just like any other part of the declaration. See Why should I avoid std::enable_if in function signatures. The choice of placement of the std::enable_if has its pros and cons.

But i am confused what is the purpose of that second argument?

Consider the example where you have a function named foo that takes some T.

template<class T> void foo(T);

You want to restrict one overload of foo whose T's have a value member equal to 1, and want to use another overload for when T::value does not equal 1. If we simply have this:

template<class T> void foo(T); // overload for T::value == 1
template<class T> void foo(T); // overload for T::value != 1

How does this convey to the compiler that you want to use two separate overloads for two separate things? It doesn't. They are both ambiguous function calls:

template<std::size_t N>
struct Widget : std::integral_constant<std::size_t, N> { };

int main() {
    Widget<1> w1;
    Widget<2> w2;

    foo(w1); // Error! Ambiguous!
    foo(w2); // Error! Ambiguous!
}

You will need to use SFINAE to reject the templates based on our condition:

template<class T, class = std::enable_if_t<T::value == 1>* = nullptr>
void foo(T); // #1

template<class T, class = std::enable_if_t<T::value != 1>* = nullptr>
void foo(T); // #2

Now the correct ones are called:

foo(w1); // OK! Chooses #1
foo(w2); // OK! Chooses #2

What makes this work is the way std::enable_if is built. If the condition in its first parameter is true, it provides a member typedef named type equal to the second parameter (defaulted to void). Otherwise, if the condition is false, it does not provided one. A reasonable implementation could be:

 template<bool, class R = void>
 struct enable_if { using type = R; };
 template<class R>
 struct enable_if<false, R> { /* empty */ };

So if the condition fails, we will be trying to access a ::type member that doesn't exist (remember that std::enable_if_t is an alias for std::enable_if<...>::type). The code would be ill-formed, but instead of it causing a hard error, during overload resolution the template is simply rejected from the candidate set and other overloads (if present) are used instead.

This is a simple explanation of SFINAE. See the cppreference page for more information.

In your case there is only one overload of is_odd and is_even, so if you pass NotIntegral to them, they will fail because their respective overloads are taken out of the candidate set, and since there are no more overloads to evaluate overload resolution fails with the aforementioned error. This could be cleaned up a bit with a static_assert message instead.

template<class T>
bool is_even (T i) {
    static_assert(std::is_integral<T>::value, "T must be an integral type");
    return !bool(i%2);
}

Now you will no longer be given a weird looking error message but your own custom one. This also rules out SFINAE since the static_assert is evaluated after template arguments are substituted, so it's your choice on which route to take. I would recommend the assertion since it's much cleaner and there is no need for SFINAE here.

Community
  • 1
  • 1
David G
  • 94,763
  • 41
  • 167
  • 253