Универсальная ссылка l-значение не копирует объект - PullRequest
1 голос
/ 20 июня 2020

Почему эти утверждения работают для приведенного ниже кода? Универсальная ссылка должна быть привязана к ссылке l-значения run(T& a) и копировать объект b из a. Однако адреса обоих объектов «a» и «b» в функции run() совпадают. Протестировано с C ++ 11/14/17 / 2a g cc -9.2 и clang ++ - 6.0. В какой части стандарта говорится, что это действительно так? не нашел ничего похожего.

#include <cassert>
#include <utility>

template <typename T>
void run(T&& a)
{
  T b {std::forward<T>(a)};
  ++b;
  assert(b == a);
  assert(&a == &b);
}

int main()
{
  int value {10};
  run(value); // asserts work, not expected
  // run(std::move(value)); // the asserts don't work as expected
}

Ответы [ 2 ]

2 голосов
/ 20 июня 2020

Однако адреса обоих объектов «a» и «b» в функции run() одинаковы.

При передаче lvalue T выводится как lvalue-ссылка, т.е. int&. (int& && сворачивает в int&, поэтому тип параметра функции a равен int&.) Затем b объявляется как привязка ссылки к a.

При передаче rvalue T выводится как int. (Таким образом, тип параметра функции a равен int&&.) Тогда b объявляется как независимая переменная, скопированная из a.

1 голос
/ 20 июня 2020

В run(value), value - это lvalue, и оно должно совпадать с T&&. Lvalue не может связываться со ссылками на rvalue, поэтому T = int и T = int&& не будут работать, как тогда T&& = int&&. Единственное, что работает, это T = int&. Из-за сворачивания ссылок ссылка rvalue на ссылку lvalue является ссылкой на lvalue, поэтому создание run выглядит так:

template<>
void run<int&>(int &a) {
    int &b{a}; // expanding std::forward
    ++b;
    assert(b == a);
    assert(&b == &a);
}

Очевидно, утверждения всегда проходят. Теперь для run(std::move(value)) аргумент действительно является r-значением, и вы получаете T = int. Тогда

template<>
void run<int>(int &&a) {
    int b{std::move(a)};
    ++b;
    assert(b == a);
    assert(&b == &a);
}

Это, конечно же, не работает. Возможно, вам следует заменить

T b{std::forward<T>(a)};

на

std::decay_t<T> b{std::forward<T>(a)};

Это удалит ссылки из T (гарантируя, что b является новым (скопированным / перемещенным) объектом), а также обработает массивы и функции (сделав b указателем, даже если a не является).

Сомневаюсь, что они вам нужны, но [temp.deduct.call]/3 говорит о шаблонном выводе ссылок пересылки, и [dcl.init.list]/3.9 говорит, что инициализация ссылки списком просто привязывает ее к элементу списка инициализатора. Также [forward], ну объясняет std::forward<T>. По сути, если T - это ссылка lvalue, то std::forward<T>(x) - это lvalue, а в противном случае - xvalue (своего рода rvalue). (В основном это условное std::move.)

...