简体   繁体   中英

How does template argument deduction distinguish between an lvalue and a literal/compile-time value

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 (eg literal: f(42) ) or an lvalue (eg local variable: f(argc) ) as its argument.

Q: How does that work ? (I expected, in both calls, that the first overload would be called (ie std::is_arithmetic<T>::value == true )

Here is a full example:

Run It Online

#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:

  1. 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& .

  2. t is a const lvalue, for example

     const int i = 1; func(i); 

    Similarly, in this case T is deduced to be const int& .

  3. 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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM