Почему std::forward возвращает static_cast‹T&&›, а не static_cast‹T›?

Пусть функция с именем Y перегружается:

void Y(int& lvalue)
{ cout << "lvalue!" << endl; }

void Y(int&& rvalue)
{ cout << "rvalue!" << endl; }

Теперь давайте определим функцию шаблона, которая действует как std::forward.

template<class T>
void f(T&& x)
{
   Y( static_cast<T&&>(x) );   // Using static_cast<T&&>(x) like in std::forward
}

Теперь посмотрим на main()

int main()
{
   int i = 10;

   f(i);       // lvalue >> T = int&
   f(10);      // rvalue >> T = int&&
}

Как и ожидалось, вывод

lvalue!
rvalue!

Теперь вернитесь к функции шаблона f() и замените static_cast<T&&>(x) на static_cast<T>(x). Посмотрим на результат:

lvalue!
rvalue!

Это то же самое! Почему? Если они одинаковые, то почему std::forward<> возвращает приведение от x к T&&?


person gedamial    schedule 07.07.2016    source источник


Ответы (1)


Классификация lvalue и rvalue остается прежней, но эффект совершенно другой (и категория значений действительно меняется, хотя и не заметно в вашем примере). Давайте рассмотрим четыре случая:

template<class T>
void f(T&& x)
{
    Y(static_cast<T&&>(x));
}

template<class T>
void g(T&& x)
{
    Y(static_cast<T>(x));
}

Если мы вызовем f с lvalue, T будет выведено как некоторое X&, поэтому ссылка приведения схлопнется X& && ==> X&, так что мы получим то же самое lvalue и ничего не изменится.

Если мы вызовем f со значением r, T будет выведено как некоторое X, поэтому приведение просто преобразует x в ссылку rvalue на x, поэтому оно становится значением r (в частности, значением x).

Если мы вызовем g с lvalue, произойдет то же самое. Нет необходимости в свертывании ссылок, так как мы просто используем T == X&, но приведение по-прежнему не выполняется, и мы по-прежнему получаем одно и то же значение lvalue.

Но если мы вызовем g со значением r, у нас будет static_cast<T>(x), которое будет копировать x. Эта копия является значением r (как подтверждает ваш тест, за исключением того, что теперь это значение prvalue, а не значение x), но в лучшем случае это дополнительная, ненужная копия, и это приведет к ошибке компиляции (если T подвижно, но некопируемый) в худшем случае. С static_cast<T&&>(x) мы приводили к ссылке, которая не вызывает копию.

Вот почему мы делаем T&&.

person Barry    schedule 07.07.2016
comment
Объясните, почему копирование происходит в static_cast? - person gedamial; 07.07.2016
comment
Ценностная категория не та. Категория значений group одинакова (rvalue), но категории значений различаются (prvalue и xvalue). - person Kerrek SB; 07.07.2016
comment
@gedamial: Что еще он мог бы сделать? Когда вы конвертируете T1 в T2, данные копируются (через преобразование) в новый объект. Этого можно избежать только с помощью ссылочных типов.... (или, для typeid(T1) == typeid(T2), используя Visual Studio в его нелепом несовместимом режиме по умолчанию ). - person Lightness Races in Orbit; 07.07.2016
comment
Если я вызываю g(int{5}), то параметр шаблона T равен int&&, а когда мы делаем static_cast<T>(x), он оценивается как static_cast<int&&>(x), который имеет тот же тип, что и x - person gedamial; 07.07.2016
comment
@gedamial: Если я вызову g(int{5}), тогда параметр шаблона T будет равен int&& Нет, это int. когда мы делаем static_cast‹T›(x), он оценивается как static_cast‹int&&›(x) Нет, это не так. И даже если бы это было так, по моей ссылке выше это не имело бы значения, если бы вы не находились в несовместимом режиме MSVS. - person Lightness Races in Orbit; 07.07.2016
comment
@LightnessRacesinOrbit Итак, в g() static_cast<T> становится static_cast<int> и... тогда? Какой тип x нам нужно преобразовать в int? - person gedamial; 07.07.2016
comment
@KerrekSB Есть ли для этого лучшее слово? Мне также не нравится группа категорий значений, поскольку lvalue — это одно, а rvalue — это две вещи. Может быть, просто наблюдаемая ценностная категория? - person Barry; 07.07.2016
comment
@gedamial static_cast<T>(e) эквивалентно T t(e); Теперь понятно, почему это копия? - person Barry; 07.07.2016
comment
@Barry Да, но я хочу понять, где несоответствие типов. Чем отличаются Т и х? - person gedamial; 07.07.2016
comment
@gedamial Это не так. Но X x; X t(x); делает копию, а X& t(x); и X&& t(std::move(x)); — нет. - person Barry; 07.07.2016
comment
@gedamial: Почему бы тебе не перейти по ссылке, которую я тебе дал. - person Lightness Races in Orbit; 07.07.2016
comment
@Barry Насчет того, чтобы типы не отличались, у меня есть еще одно сомнение. На этой странице isocpp.org/blog/2012/11 / Скотт Мейерс говорит: param инициализируется литералом 10, который является rvalue (...) универсальная ссылка param инициализируется rvalue, поэтому param становится ссылкой rvalue – в частности, int&& param в моем случай x Но если x имеет тип T&&, а T имеет тип int, то в static_cast<T>(x) T и x не совпадают!? Если я вызову g(10), T будет int, а x будет int&&, и это не одно и то же - person gedamial; 07.07.2016
comment
@gedamial Вы везде путаете типы и значения, и я не понимаю, что вы говорите. static_cast<int>(x) скопировал бы int, да. - person Barry; 07.07.2016
comment
@Barry Если я вызываю g (10), этот литерал является значением r, поэтому внутри функции T оценивается как int, тогда как параметр x оценивается как T&&, что равно int&& Итак, вы видите? static_cast<T>(x) с точки зрения типов должно оцениваться (как говорит Скотт Мейерс) как static_cast<int>(int&&) - person gedamial; 07.07.2016
comment
@gedamial А? Это не вопрос типа. Это вопрос эффективности. static_cast<T>(x) сделает дополнительную копию, static_cast<T&&>(x) нет. Нигде в этой статье Мейерс не делает этого неправильно. - person Barry; 07.07.2016
comment
@ Барри, я не говорю о копировании или не копировании. Я говорю о том, чем на самом деле являются T и x! T это int, а x это int&&. Это правда? Если нет: почему? Скотт Мейерс говорит - person gedamial; 07.07.2016
comment
@gedamial Да. Если x является rvalue типа int, то T будет int, а decltype(x) будет int&&. - person Barry; 07.07.2016
comment
@Барри Спасибо. Итак, теперь, когда мы это прояснили, мы можем сказать, что в функции g() происходит копирование, потому что static_cast<int>(int&&) похоже на выражение X&& x; X t(x);, которое является допустимым контекстом для копирования. Правильно? - person gedamial; 07.07.2016
comment
@gedamial Я не знаю, что вы подразумеваете под действительным контекстом для копии. Это копия, и точка. - person Barry; 07.07.2016
comment
@Barry, OP: Конечно, это не только вопрос эффективности; если вы замените int в коде исходного вопроса OP на unique_ptr<int>, он не скомпилируется. - person Kyle Strand; 08.07.2016
comment
@gedamial ... если x имеет тип T&& ... тогда ... T и x не одно и то же !? Правильный; T&& — это не то же самое, что T, потому что один из них — ссылка, а другой — значение. static_cast<int>(int&&) все равно, что сказать X&& x; X t(x); ... Да, потому что static_cast<T> создает значение типа T, согласно первому комментарию Lightness, а здесь T не является ссылочным типом, поэтому фактически создается (копируется) int. - person Kyle Strand; 08.07.2016
comment
Всем большое спасибо, вы молодцы =) - person gedamial; 08.07.2016