This is a question related to OP's solution to Is constexpr useful for overload.
Basically, he used
template<class T>
typename std::enable_if<std::is_arithmetic<T>::value, int>::type
f(T&& n) { ... }
and
template<class T>
typename std::enable_if<!std::is_arithmetic<T>::value, int>::type
f(T&& n) { ... }
to know whether f()
has been called with is a compile-time variable (e.g. literal: f(42)
) or an lvalue (e.g. local variable: f(argc)
) as its argument.
Q: How does that work ? (I expected, in both calls, that the first overload would be called (i.e. std::is_arithmetic<T>::value == true
)
Here is a full example:
#include <iostream>
#include <type_traits>
using std::cout;
using std::endl;
template<class T>
constexpr
typename std::enable_if<std::is_arithmetic<T>::value,
int>::type
inline f(T&& n)
{
//cout << "compile time" << endl;
return 1;
}
template<class T>
typename std::enable_if<!std::is_arithmetic<T>::value,
int>::type
inline f(T&& n)
{
//cout << "run time" << endl;
return 0;
}
int main(int argc, char* argv[])
{
const int rt = f(argc);
constexpr int ct = f(42);
cout << "rt: " << rt << endl;
cout << "ct: " << ct << endl;
}
A template function of the form
template <typename T>
void func(T&& t);
looks as if it takes an r-value reference. But in actual fact T&&
here is what Scott Meyers calls a universal reference, otherwise known as a forwarding reference. Different things can happen depending on the value category of the argument. Let's have a look at each case:
t is a non-const lvalue, for example
int i = 0;
func(i);
In this case, T is deduced to be an lvalue reference to int
, that is, T=int&
.
t is a const lvalue, for example
const int i = 1;
func(i);
Similarly, in this case T
is deduced to be const int&
.
t is an rvalue, for example
func(1);
In this case, T
is deduced to be int
just as we might have expected
Exactly why these deductions happen this way is to do with the rules for reference collapsing; I highly recommend reading Scott Meyers' article on the subject if you're interested.
The last case above also illustrates the point that in C and C++, literals (except string literals) are always rvalues.
What does this have to do with the enable_if
? Well if your f
is called with an integer literal, then T
is deduced to be plain int
. Obviously, is_arithmetic<int>
is true, so the second function gets SFINAE'd out and the first is called.
However, when called with an lvalue, T
is deduced to be (const) int&
. A reference is not arithmetic, so the first function disappears leaving only the second to be called.