Several times I’ve found myself using templates because I wanted to use different method implementations depending on properties of the type. Doing a quick google search it seems that I’m not the only one using templates for that same reason, so in this post I’m going to try to show you how we can use C++ to choose different method implementations at compile time based on the type(s), and explain the mechanisms that allow us to do it.
First, let’s consider an example: we want to create a method, compare, that compares two arbitrary objects. The method should return true if the objects are equal, and false if the objects are not equal or can’t be compared.
So this is how we would use that method:

int b{0};
std::vector<float> v;
compare(b, b); //Should return true: objects are comparable and equal
compare(b, v); //Should return false: objects are not comparable
compare(b, 2); //Should return false: objects are not equal

I’m going to show you now a possible implementation of compare, and we will go through this implementation explaining all the bits and pieces.
This is how compare could look like:

bool compare( … )
{
std::cout << "Can’t compare" << std::endl;
return false;
}
template<typename T1, typename T2>
auto compare( const T1 &a, const T2 &b) -> decltype((void)(a == b), bool())
{
std::cout << "Can compare" << std::endl;
return a == b;
}

This is not trivial, so let’s walk through the code and see what’s going on:

template<typename T1, typename T2>
auto compare( const T1 &a, const T2 &b) -> decltype((void)(a == b), bool())

A few things happening here, let’s go step by step. 
First, in C++11 we have a new way of declaring a function (see number 2):

auto foo(int b) -> bool; //same as bool foo(int b);

Basically here auto doesn’t perform any type detection, the return type will be whatever is after ->, in this scenario we can consider auto as a mere placeholder.
That means that here:

template<typename T1, typename T2>
auto compare( const T1 &a, const T2 &b) -> decltype((void)(a == b), bool())

The return type of compare is whatever type decltype((void)(a == b), bool()) yields.
Now let’s try to understand what are we doing with decltype.
As we know, decltype yields the type of whatever we pass to it.

std::vector<int> v;
decltype(v) v2; //decltype(v) yields std::vector<int>, so this line is equivalent to std::vector<int> v2;
float b;
auto foo(int b) -> decltype(b); //foo returns the type of b, in this case float, so this is equivalent to float foo(int b);

But we can use decltype not only with entities, but also with expressions, and here is where things get interesting. When we use decltype with expressions, decltype will yield the type returned by the expression.
Let’s see some examples of decltype used with expressions:

float foo();
decltype(foo()) b; //decltype(foo()) yields the type of whatever foo() returns, in this case, float
std::vector<int> b;
auto bar() -> decltype(b.push_back(int())); //decltype yields void, so this is the same as void bar();

It’s important to mention that when you use an expression inside decltype, the expression will be evaluated, but not executed. This means that, in the second example, where we write decltype(b.push_back(int())), we are not pushing anything into the vector, we are just taking the type returned by that expression.
However, in our example, we have not one but two expressions:
decltype((void)(a == b), bool())

Well, this is actually just one expression. The comma operator is just another way to create expressions. Quoting the standard:

[…] A pair of expressions separated by a comma is evaluated left-to-right; the left expression is a discarded-value expression (Clause 5). Every value computation and side effect associated with the left expression is sequenced before every value computation and side effect associated with the right expression. The type and value of the result are the type and value of the right operand; the result is of the same value category as its right operand, and is a bit-field if its right operand is a glvalue and a bit-field. If the value of the right operand is a temporary (12.2), the result is that temporary.

So, basically, when we have two expressions separated by a comma, the first expression is evaluated (not executed), the result is discarded and then the second expression is evaluated and it’s value is returned.
So this is how the expression inside decltype
(void)(a == b), bool()

is evaluated: first a==b is evaluated, then the return type is casted to void (I will explain this in a moment), and then the result of the second expression, bool(), is what the expression is evaluated to. So, basically, this is what decltype sees (if a==b is defined, more on this in a moment):
decltype(bool()); //equivalent to just write bool

Now, why am I casting to void? Because classes can override the comma operator, that means that, if the result of the first expression is a class that overloads the comma operator, then the type yielded by decltype would be the type returned by the comma operator.
Let’s make it more clear with an example:

class A
{
public:
std::vector<int> operator,( bool v )
{
std::cout << "Comma operator" << std::endl;
return { 0 };
}
friend inline A operator==( const A &a, int b );
};
inline A operator==(const A &a, int b)
{
return A();
}
bool compare( … )
{
std::cout << "Can’t compare" << std::endl;
return false;
}
template<typename T1, typename T2>
auto compare( const T1 &a, const T2 &b) -> decltype(a == b, bool())
{
return true;
}
int main()
{
A a;
auto r = compare( a, 2 );
return 0;
}

This example wouldn’t compile and we will see the following error:

Error C2440 ‘=’: cannot convert from ‘std::vector<int,std::allocator<_Ty>>’ to ‘bool’

Let’s analyse why.
compare is used here

A a;
auto r = compare( a, 2 );

We are calling compare with the types A and int.
Let’s see what’s going on with our decltype statement.
decltype(a == b, bool())
Here, A overloads the operator==(A, int), and make it returns an instance of A, so we have this
decltype(A, bool())
But it turns out that A also overloads the comma operator taking a bool, so the previous line is really:
decltype(A::operator,(bool()))
And operator,(bool) from A returns an object of type std::vector<int>, so our method declaration is translated to this:

auto compare( const A&a, const int &b) -> decltype(std::vector<int>)
{
return true;
}

And that’s why we see the compiler error: the declaration states that compare returns an object of type std::vector<int>, but in the method we return true, therefore the compiler complains because there’s no conversion between bool and std::vector<int>.
So, in order to avoid this kind of situations, we cast the result of the first expression to void, and we get rid of this kind of problems with the comma operator. If we just add the cast to void to the code in the example, it will compile fine:

template<typename T1, typename T2>
auto compare( const T1 &a, const T2 &b) -> decltype((void)(a == b), bool())

Great! Finally we know what’s going on on these lines

template<typename T1, typename T2>
auto compare( const T1 &a, const T2 &b) -> decltype((void)(a == b), bool())
{
std::cout << "Can compare" << std::endl;
return a == b;
}

But there is a problem: what happens when the comparison between T1 and T2 is not defined?

template<typename T1, typename T2>
auto compare( const T1 &a, const T2 &b) -> decltype((void)(a == b), bool())
{
std::cout << "Can compare" << std::endl;
return a == b;
}
int main()
{
auto r = compare(2, std::vector<int>{1}); //comparison between int and std::vector<int>
}

Now it’s time to talk about SFINAE.
SFINAE stands for Substitution Failure Is Not An Error
In C++11, the standard says that when a substitution failure (like the one that is happening above when we try to compare a vector and an int) occurs, type deduction for that particular type fails (in this case, the type deduction of the expression a==b, where a is an instance of std::vector<int> and b is an int). And that’s it, no error is thrown, the compiler just ignores the candidate and looks at the others.
There is a lot to say about SFINAE, if you are interested I recommend you to read more about it.
Let’s back to our code:

bool compare( … )
{
std::cout << "Can’t compare" << std::endl;
return false;
}
template<typename T1, typename T2>
auto compare( const T1 &a, const T2 &b) -> decltype((void)(a == b), bool())
{
std::cout << "Can compare" << std::endl;
return a == b;
}
int main()
{
auto r = compare(2, std::vector<int>{1}); //comparison between int and std::vector<int>
}

Now we understand what’s going on here: The compiler sees two definitions for compare, and it first tries with the one that is templated (you need to know that ellipsis parameter has the lowest ranking for overload resolution, so compare(…) will be the last overload that that the compiler will try to use), however, when the compiler reaches this point:
decltype((void)(a == b), bool())

It can’t deduce the type yielded by a == b, because == is not defined between std::vector<int> and int, and thanks to SFINAE, the compiler won’t throw an error, but instead it will ignore this implementation and will look at the other definition and use that one (by the way, that means that if this overload of compare wouldn’t be present, the compiler will throw an error saying that it can’t find a suitable overload for compare)

bool compare( … )
{
std::cout << "Can’t compare" << std::endl;
return false;
}

Great! Now we understand what’s going on in our code.

However, I have bad news. This solution is awful. Using the ellipsis parameters is bad for several reasons: the arguments are copied (so you can’t use it with classes that are not copyable), you have to unpack the parameters, etc. Can’t we implement a better solution?
The good news is that the answer is yes, there is a better way of doing this.

std::enable_if

enable_if is a very simple yet a powerful tool. enable_if is a metafunction defined like this:

template< bool B, class T = void >
struct enable_if;

If the first argument is true, then enable_if has a static member of type T (the second argument, which is void by default) called type. If the first argument is false, then this static member type does not exist.
Let’s see an example:

std::enable_if<true, int>::type; //type is int
std::enable_if<false, int>::type; //error: type is not defined

The power of enable_if comes thanks to SFINAE.

static const bool foo_impl = true;
auto foo() -> decltype(typename std::enable_if<foo_impl, bool>::type)
{
std::cout << "Foo 1" << std::endl
}
auto foo() -> decltype(typename std::enable_if<!foo_impl, bool>::type)
{
std::cout << "Foo 2" << std::endl;
}

In this example, thanks to both enable_if and SFINAE, we can control which version of foo is used. When foo_impl is true, the first definition of foo is used, because the second one fails (because the compiler sees std::enable_if<false, bool>::type, but as we said before, if the first argument is false, then type is not defined), and viceversa.

So, how can we use enable_if in our case? We need something (let’s call this something is_comparable) that gives us true when T1 and T2 can be compared, and false when they can’t, and then use it with enable_if in the same way that we have used foo_impl in our previous example

template<typename T1, typename T2>
auto compare( const T1 &a, const T2 &b )->typename std::enable_if<is_comparable<T1, T2>::value, bool>::type //is_comparable<T1,T2>::value should be true if T1 and T2 can be compared
{
std::cout << "Can compare" << std::endl;;
return a == b;
}
template<typename T1, typename T2>
auto compare( const T1 &a, const T2 &b )->typename std::enable_if<!is_comparable<T1, T2>::value, bool>::type //is_comparable<T1,T2>::value should be false if T1 and T2 can't be compared
{
std::cout << "Can’t compare" << std::endl;
return false;
}

Now the question is how to implement is_comparable<T1, T2>. Here is my proposal

template<typename T1, typename T2, typename T3 = void>
struct is_comparable
{
static const bool value = false;
};
template<typename T1, typename T2>
struct is_comparable<T1, T2, decltype((void)(std::declval<T1>() == std::declval<T2>()))>
{
static const bool value = true;
};

A lot of stuff going on here, let’s go step by step.
We have the main definition of is_comparable, where the third parameter is defaulted to void

template<typename T1, typename T2, typename T3 = void>
struct is_comparable
{
static const bool value = false;
};

And then a specialization of is_comparable

template<typename T1, typename T2>
struct is_comparable<T1, T2, decltype((void)(std::declval<T1>() == std::declval<T2>()))>
{
static const bool value = true;
};

Where T3 is defined only if std::declval<T1>() == std::declval<T2>() can be deduced (and, if it can be deduced, we cast it to void).

How does it work? Well, the only thing that we haven’t seen before is std::declval.
std::declval<T1>() == std::declval<T2>()

std::declval converts any type T to a reference type, meaning that we can use member functions, access to members, etc. like if there was an instance of an object (but there’s not, no object is constructed). So we can say that when we do std::declval<T>() we get the equivalent of an instance of T

std::declval is very useful for templates, because as we said, we can access to member functions like if we had an instance:

decltype(std::declval<MyClass>().foo()) a; //a will be an instance of the type returned by MyClass::foo(), if MyClass::foo() exists. If it does not exist, the program wouldn't compile.

In our case, we are using it to check if we can compare two objects of type T1 and T2.
Now we are in a position to understand what’s happening here:

template<typename T1, typename T2, typename T3 = void>
struct is_comparable
{
static const bool value = false;
};template<typename T1, typename T2>
struct is_comparable<T1, T2, decltype((void)(std::declval<T1>() == std::declval<T2>()))>
{
static const bool value = true;
};

The compiler sees a definition of is_comparable and a specialization of is_comparable. First, the compiler considers the specialization (because specializations are always considered better matches). The specialization will be defined only if std::declval<T1>() == std::declval<T2>() is defined. If std::declval<T1>() == std::declval<T2>() is not defined, then SFINAE will kick in, and the compiler will ignore the specialization (because it’s ill-formed) and will use the non-specialized version (which defines value to false).

You might have the following question: why are we casting to void?

template<typename T1, typename T2>
struct is_comparable<T1, T2, decltype((void)(std::declval<T1>() == std::declval<T2>()))>
{
static const bool value = true;
};

This is not the same situation as before with the comma operator. Here, we are casting to void because the types of default template parameters have to match, or the specialization is not taken into account. So, because the default type T3 is void in the non-specialized version of is_comparable, we need to make sure that T3 is also void in the specialization, otherwise the specialization won’t be considered.

That’s great! Now we can put everything together:

template<typename T1, typename T2, typename T3 = void>
struct is_comparable
{
static const bool value = false;
};
template<typename T1, typename T2>
struct is_comparable<T1, T2, decltype((void)(std::declval<T1>() == std::declval<T2>()))>
{
static const bool value = true;
};
template<typename T1, typename T2>
auto compare( const T1 &a, const T2 &b )->typename std::enable_if<is_comparable<T1, T2>::value, bool>::type
{
std::cout << "Can compare" << std::endl;;
return a == b;
}
template<typename T1, typename T2>
auto compare( const T1 &a, const T2 &b )->typename std::enable_if<!is_comparable<T1, T2>::value, bool>::type
{
std::cout << "Can't compare" << std::endl;
return false;
}
int main()
{
int b{ 0 };
std::vector<float> v;
auto r = compare( v, v ); //are comparable: vector<int> and vector<int>

r = compare( b, v ); //not comparable: int and vector<int>
r = compare( b, 2 ); //comparable: int and int
  return 0;
}

If we run this program we will see:

Can compare
Can't compare
Can compare

Yay!

With all the knowledge that we have, now it’s easy to create different implementations that do different things depending on type properties.
For example, now it’s easy to create a function check_foo that will print out different things depending on whether the type passed has a member called foo or not.

template<typename T1, typename T2 = void>
struct has_foo
{
static const bool value = false;
};
template<typename T1>
struct has_foo < T1, decltype((void)(std::declval<T1>().foo)) >
{
static const bool value = true;
};
template<typename T1>
auto check_foo( const T1 &a )->typename std::enable_if<has_foo<T1>::value>::type
{
std::cout << "Has foo" << std::endl;
}
template<typename T1>
auto check_foo( const T1 &b)->typename std::enable_if<!has_foo<T1>::value>::type
{
std::cout << "Hasn't foo" << std::endl;
}
struct B
{
int foo;
};
int main()
{
int a{ 0 };
B b;
check_foo( a );
check_foo( b );
}

I hope that this post was useful to you, we have seen a lot of stuff (SFINAE, comma operator, decltype, declval, etc) but finally we managed to get something working! Not only that, we have seen how this approach can be used to create different functions that do different things depending on properties of the types that are used.
If you have any question or just want to say something, please write a comment!

Thank you!