Предотвращение неконстантных 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>&&)
Ответы
Ответ 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&&
шаблона функции для привязки к любой категории аргументов - это то, что позволяет совершенную пересылку.