Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C++

enable_if

5.00/5 (1 vote)
21 Feb 2015CPOL4 min read 10.6K  
This post discusses enable_if.

A few weeks ago, I wrote an entry on SFINAE[^], and I mentioned enable_if. At that point, I had never been able to successfully apply enable_if. I knew this construct was possible because of SFINAE, however, I was letting the differences between SFINAE and template specialization confuse me. I now have a better understanding and wanted to share my findings.

What Is It?

As I mentioned before, enable_if is a construct that is made possible because of SFINAE. enable_if is a meta-function that can help you conditionally remove functions from the overload resolution performed on the type-traits supplied to the function.

You can find examples of its usage in the Standard C++ Library within the containers themselves, such as std::vector:

C++
template< class _Iter>
  typename enable_if< _Is_iterator<_Iter>::value,
                      void>::type
assign(_Iter _First, _Iter _Last)
{ ... }

void 
assign(size_type _Count, const value_type& _Val)
{ ... }

What does the previous block of code accomplish? The assign call will always exist if you want to specify a count and supply a value that is a value_type. However, the conditional version will only be declared if the parameterized type has a type_trait that evaluates to an iterator-type. This adds flexibility to definition of this function, and allows many different instantiations of this function; as long as the type is an iterator.

Also note that the template definition has a completely different signature than the other non-parameterized version of assign. This is an example of overload resolution. I think it is important to point this out, because I was getting caught up into how it should be used. As I perused the Internet forums, it seems that many others are confused on this point as well.

How It Works

Let's continue to look at the std::vector. If the parameterized type is not an iterator type, the enable_if construct will not allow the parameterized type to exist. Because this occurs in the template parameter selection phase, it uses SFINAE to eliminate this function as a candidate to participate in the overloaded selection of assign.

Let's simplify this example even further. Imagine the second version of assign does not exist; the only definition is the parameterized version that uses enable_if. Because it is a template, potentially many instantiations of this function will be produced. However, the use of enable_if ensures that the function will only be generated for types that pass the iterator type-trait test.

This protects the code from attempting to generate the assign function when integers, floats or even user defined object types. Essentially, this usage is a form of concept checking that forces a compiler error if the type is not an iterator. This prevents invalid code with types that appears to compile cleanly from getting a chance to run if it will produce undefined behavior.

Another Example

Previously, when I attempted to use enable_if, I tried to apply it in a way that reduced to this:

C++
class Sample
{
public:
  template< typename = typename std::enable_if< true >::type >
  int process()
  {
    return 1;
  }

  template< typename = typename std::enable_if< false >::type >
  int process()
  {
    return 0;
  }
};

Hopefully, something appears wrong to you in the example above. If we eliminate the template declarations, and the function implementations, this is what remains:

C++
//template< typename = typename std::enable_if< true >::type >
int process()
// ...

//template< typename = typename std::enable_if< false >::type >
int process()
// ...

// This declaration will always trigger an error for a non-existent type:
//     std::enable_if< false >::type

When the compiler processes the object, it may not instantiate every function, however, it does catalog every definition. Even though there is a definition for a false case that should not result in the instantiation of that version, it does not affect the overload resolution of the functions. The enable_if definition needs to contain a type that is dependent on the expression. This line will always be invalid:

Types of Usage

There are four places enable_if can be used:

Function Return Type

This is a common form. However, it cannot be used with constructors and destructors. Typically, one of the input parameters must be dependent on a parameterized type to enable multiple function signatures. Overload resolution cannot occur on the return-type alone.

C++
// Only calculate GCD for 
// type T that is an integer.
template< typename T >
typename std::enable_if< std::is_integral< T >::value, T >::type
gcd(T lhs, T rhs)
{
  // ...
}

Function Argument

This format looks a little awkward to me. However, it works for all cases except operator overloading.

C++
// Only calculate GCD for 
// type T that is an integer.
template< typename T >
T gcd(T lhs, T rhs, typename std::enable_if< std::is_integral< T >::value, T >::type* = 0)
{
  // ...
}

Class Template Parameter

This example is from cppreference.com, and it indicates that static_assert may be a better solution that the class template parameter. Nonetheless, here it is:

C++
// A is enabled via a template parameter
template< class T, class Enable = void>
class A; // undefined
 
template< class T>
class A< T , typename std::enable_if<std t="">::value >::type> {
}; // note: for this use case, static_assert may be more appropriate

// usage:
//  A< int > a1; // compile-time error
    A< double > a1; // OK

Plate Parameter

This is probably the cleanest and most universal form the enable_if construct can be used. There are no explicit types for the user to define because the template parameter will be deduced. While the same amount of code is required in this form, the declaration is moved from the function parameter list to the template parameter list.

Personally, I like this form the most.

C++
// Only calculate GCD for 
// type T that is an integer.
template< typename T,
          typename std::enable_if< std::is_integral< T >::value, 
                                  T >::type*=nullptr >
T gcd(T lhs, T rhs)
{
  // ...
}

Summary

When learning new tools, it is often difficult to see how they are applied. We spent years practicing in math, as we attempted to solve the numerous story problems with each new mathematic abstraction taught to use. Writing code and using libraries is very similar. enable_if is a construct that is best used to help properly define functions that meet concept criteria for a valid implementation. Think of it as a mechanism to help eliminate invalid overload possibilities rather than a tool to allow the selection between different types and your application of this meta-function should become simpler.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)