Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why named variable calls are resolved to T&& instead of const T&?

Tags:

c++

c++11

As the title says, why named variables calls are resolved to T&& instead of const T& functions?

#include <iostream>

template<typename T>
void f(T&& v)
{
    std::cout << "void f(T&& v)" << std::endl;
}

template<typename T>
void f(const T& v)
{
    std::cout << "void f(const T& v)" << std::endl;
}

int main()
{
    int i = 0;

    f(1);
    f(i);
}

In this case both calls are resolved to first version of f(), even if i is named. One solution would be to add also:

template<typename T>
void f(T& v)
{
    std::cout << "void f(T& v)" << std::endl;
}

or to change first version to:

template<typename T>
typename std::enable_if<!std::is_reference<T>::value, void>::type f(T&& v)
{
    std::cout << "void f(T&& v)" << std::endl;
}

but I want to understand the reasons behind this decision.

like image 267
Mircea Ispas Avatar asked Mar 23 '14 00:03

Mircea Ispas


2 Answers

The deduction is T = int &, which means f(T &&) == f(int &). The overload resolution rules ([over.ics.rank/13.3.3.2]) say that this is is a strictly better match than f(int const &). Both are classified as an "exact match" (binding a value to a reference), but the less CV-qualified reference is preferred.

like image 66
Kerrek SB Avatar answered Oct 14 '22 15:10

Kerrek SB


A less standardese-encumbered answer would be to recognise that in the declaration:

template<typename T>
void f(T&& v)

Because T is a deduced type, T&& v is what Scott Meyers calls a universal reference, which can bind to both rvalues and lvalues. T can be deduced to be a reference type, so you are actually calling f<int&>(int& && v), at which point the reference-collapsing rules come into effect, making the nominal signature of this function f<int&>(int& v) which, as the previous answer noted, is a better match for a non-const int argument.

In Meyers' upcoming Effective Modern C++ he has the following items:

  • Item 30: Pass and return rvalue references via std::move, universal references via std::forward.
  • Item 31: Avoid overloading on universal references.

Item 31 tells you what not to do. Item 30 suggests what you could do instead, write only one overload of f, and perfectly forward v with std::forward:

template<typename T>
void f(T&& v)
{
    std::cout << "void f(T&& v)" << std::endl;

    if (std::is_rvalue_reference<decltype(v)>::value)
        std::cout << "rvalue" << std::endl;
    else
        std::cout << "lvalue" << std::endl;

    std::cout << "v = " << std::forward<T>(v) << std::endl;
}
like image 40
Oktalist Avatar answered Oct 14 '22 14:10

Oktalist