Подтвердить что ты не робот

Предотвращение неконстантных lvalues ​​от ссылки на rvalue вместо ссылки const lvalue

У меня возникли проблемы с перегрузкой функции, чтобы получить значение либо с помощью ссылки const, либо, если это rvalue, ссылка rvalue. Проблема в том, что мои не-const lvalues ​​являются обязательными для версии функции rvalue. Я делаю это в VC2010.

#include <iostream>
#include <vector>

using namespace std;

template <class T>
void foo(const T& t)
{cout << "void foo(const T&)" << endl;}

template <class T>
void foo(T&& t)
{cout << "void foo(T&&)" << endl;}

int main()
{
    vector<int> x;
    foo(x); // void foo(T&&) ?????
    foo(vector<int>()); // void foo(T&&)
}

Приоритет заключается в том, чтобы вывести foo (x) как

foo< vector<int> & >(vector<int>& && t)

вместо

foo< vector<int> >(const vector<int>& t)

Я попытался заменить версию rvalue-reference на

void foo(typename remove_reference<T>::type&& t)

но это только привело к тому, что все было разрешено для ссылочной версии const-lvalue.

Как предотвратить это поведение? И почему это так по умолчанию - кажется настолько опасным, что разрешено изменять значения rvalue, это оставляет меня с неожиданно измененной локальной переменной.

EDIT: просто добавлены версии без шаблонов функций, и они работают должным образом. Создание функции шаблона изменяет правила разрешения перегрузки? Это... действительно расстраивает!

void bar(const vector<int>& t)
{cout << "void bar(const vector<int>&)" << endl;}

void bar(vector<int>&& t)
{cout << "void bar(vector<int>&&)" << endl;}

bar(x); // void bar(const vector<int>&)
bar(vector<int>()); // void bar(vector<int>&&)
4b9b3361

Ответ 1

Если у вас есть шаблонная функция, подобная этой, вы почти никогда хотите перегрузить. Параметр T&& является параметром catch. И вы можете использовать его, чтобы получить любое поведение, которое вы хотите, из одной перегрузки.

#include <iostream>
#include <vector>

using namespace std;

template <class T>
void display()
{
    typedef typename remove_reference<T>::type Tr;
    typedef typename remove_cv<Tr>::type Trcv;
    if (is_const<Tr>::value)
        cout << "const ";
    if (is_volatile<Tr>::value)
        cout << "volatile ";
    std::cout << typeid(Trcv).name();
    if (is_lvalue_reference<T>::value)
        std::cout << '&';
    else if (is_rvalue_reference<T>::value)
        std::cout << "&&";
    std::cout << '\n';
}

template <class T>
void foo(T&& t)
{
    display<T>();
}

int main()
{
    vector<int> x;
    vector<int> const cx;
    foo(x); // vector<int>&
    foo(vector<int>()); // vector<int>
    foo(cx);  // const vector<int>&
}

Ответ 2

Чтобы T&& связываться с ссылкой на lvalue, T должен сам быть ссылочным типом lvalue. Вы можете запретить создание шаблона с помощью ссылочного типа T:

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

enable_if находится в <utility>; is_reference находится в <type_traits>.

Причина, по которой перегрузка, принимающая T&&, предпочтительнее перегрузки с использованием T const&, заключается в том, что T&& является точным соответствием (с T = vector<int>&), но T const& требует преобразования квалификации (const-qualification должна добавляется).

Это происходит только с шаблонами. Если у вас есть функция nontemplate, которая принимает std::vector<int>&&, вы сможете вызвать эту функцию только с аргументом rvalue. Когда у вас есть шаблон, который принимает T&&, вы не должны думать об этом как "параметр ссылки rvalue"; это "универсальный эталонный параметр" (Скотт Мейерс использовал похожий язык, я полагаю). Он может принять что угодно.

Разрешить параметр T&& шаблона функции для привязки к любой категории аргументов - это то, что позволяет совершенную пересылку.