Есть ли приведение (или стандартная функция) с эффектом, противоположным `std::move`?

Во-первых, этот вопрос не является дубликатом двойной функции для std::move? или существует ли обратный std::move?, Я не спрашиваю о механизме, предотвращающем перемещение в ситуации, когда это могло бы иметь место, и вместо этого о копировании; скорее, я спрашиваю о механизме принятия значения в позиции, которая будет привязана к изменяемой ссылке на значение. Это на самом деле полная противоположность ситуации, для которой std::move был изобретен (а именно, сделал изменяемое значение l принятым в положении, которое будет связано с (изменяемой) ссылкой на значение).

В ситуации, которая меня интересует, значение не будет принято, потому что контекст требует изменяемой ссылки на значение. По какой-то причине, которую я не совсем понимаю, но хочу принять, выражение (модифицируемое) rvalue будет связываться с константной ссылкой lvalue (без введения дополнительного временного), но оно не будет привязываться к модифицируемой ссылке lvalue (сообщение об ошибке, которое дает мне gcc, это "неверная инициализация неконстантной ссылки типа" A& "из значения r типа" A "", в то время как clang говорит, что "неконстантная ссылка lvalue для типа" A "не может привязаться к временному тип "A" "; любопытно, что я не могу заставить ни один из этих компиляторов признать, что рассматриваемое выражение имеет тип" A&& ", даже если это выражение действительно имеет форму static_cast<A&&>(...) что само по себе не вызывает ошибок). Я могу понять, что обычно не хотелось бы принимать выражение rvalue в позиции, требующей модифицируемой ссылки lvalue, поскольку это подразумевает, что любые изменения, сделанные через эту ссылку lvalue, будут потеряны, но просто как вызов std::move говорит компилятору: "Я знаю, что это lvalue, которое будет связано со ссылкой на rvalue (параметром) и, следовательно, может быть украдено, но я знаю, что я делаю, и здесь все в порядке". Я хотел бы сказать, в моем случае "я знаю, что это временно, что будет связано с изменяемой ссылкой lvalue (параметром), и поэтому любые изменения, которые будут сделаны через ссылку lvalue, исчезнут незамеченными, но я знаю, что я делаю, и это нормально Вот".

Я могу решить эту проблему, инициализировав именованный объект типа A из rvalue, а затем предоставив имя, для которого требуется модифицируемая ссылка lvalue. Я не думаю, что для этого есть какие-то дополнительные издержки времени выполнения (в любом случае для rvalue потребовалось временное использование), но делать это неудобно по нескольким причинам: вводить фиктивное имя, возможно, просто вводить составное выражение чтобы сохранить объявление, отделяя выражение, создающее значение r от вызова функции, для которого он предоставляет аргумент. Откуда у меня вопрос, можно ли это сделать без введения фиктивного имени:

  1. Есть ли способ (например, с использованием приведения) связать выражение rvalue типа A с модифицируемой ссылкой lvalue типа A& без введения именованного объекта типа A?
  2. Если нет, это сознательный выбор? (и если да, то почему?) Если есть, есть ли механизм, аналогичный std::move предусмотрено стандартом для облегчения этого?

Вот упрощенная иллюстрация, где мне нужно такое преобразование. Я намеренно удалил специальные конструкторы A, чтобы убедиться, что сообщение об ошибке не включает временные значения, которые компилятор решил ввести. Все ошибки исчезают, когда A& заменены на const A&,

class A
{ int n;
public:
  A(int n) : n(n) {}
  A(const A&) = delete; // no copying
  A(const A&&) = delete; // no moving either
  int value() const { return n; }
};

int f(A& x) { return x.value(); }

void g()
{ A& aref0 = A(4); // error
  // exact same error with "= static_cast<A&&>(A(4))" instead of A(4)
  A& aref1 = static_cast<A&>(A(5)); // error
  // exact same error with "= static_cast<A&&>(A(5))" instead of A(5)
  f (A(6)); //error
  // exact same error with "= static_cast<A&&>(A(6))" instead of A(6)

  A a(7);
  f(a); // this works
  A& aref2 = a; // this works too, of course
}

Для тех, кому интересно, зачем мне это, вот один из вариантов использования. У меня есть функция f с параметром, который служит входным аргументом, а иногда также и выходным аргументом, заменой предоставленного значения на "более специализированное" значение (значение представляет собой древовидную структуру, и некоторые отсутствующие ветви могли быть заполнены); поэтому это значение передается как модифицируемая ссылка lvalue. У меня также есть некоторые глобальные переменные, содержащие значения, которые иногда используются для предоставления значения для этого аргумента; эти значения неизменны, потому что они уже полностью специализированы. Несмотря на эту постоянную природу, я привык не объявлять эти переменные const, поскольку это сделало бы их непригодными в качестве аргумента. Но на самом деле предполагается, что они являются глобальными и постоянными константами, поэтому я хотел переписать свой код, чтобы сделать это явным, а также избежать случайных ошибок при изменении реализации f (например, он может решить отказаться от своего аргумента при создании исключения; все будет в порядке, если аргумент представляет локальную переменную, которая в любом случае будет уничтожена этим исключением, но будет иметь катастрофические последствия, если он будет связан с глобальным " константа "). Поэтому я решил сделать копию всякий раз, когда одна из этих глобальных констант передается f, Есть функция copy который делает и возвращает такую ​​копию, и я хотел бы вызвать его в качестве аргумента f; Увы copy(c) будучи rvalue, это не может быть сделано по причинам, объясненным выше, даже при том, что это использование совершенно безопасно, и фактически более безопасно чем мое предыдущее решение.

4 ответа

Решение

Самое простое решение:

template<typename T>
T& force(T&& t){
   return t;
}

Функция, представленная ниже, является плохой идеей. Не используйте это. Это обеспечивает очень легкий путь к висящим ссылкам. Я считаю код, который нуждается в нем, и действую соответствующим образом.

Тем не менее, я думаю, что это интересное упражнение по какой-то причине, поэтому я не могу не показать его.

Внутри функции имена ее аргументов являются lvalues, даже если аргументы являются ссылками rvalue. Вы можете использовать это свойство для возврата аргумента в качестве ссылки на lvalue.

template <typename T>
constexpr T& as_lvalue(T&& t) {
    return t;
};

Оставьте идеальную пересылку из своей функции пересылки, и вы получите no_move:

template<class T> constexpr T& no_move(T&& x) {return x;}

Просто убедитесь, что вы делаете вид, что у вас есть ссылка на lvalue вместо ссылки на rvalue - это нормально, так как это обходит защиту от привязки временных файлов к неконстантным ссылкам и тому подобному.

Любой код, использующий эту функцию, почти наверняка неисправен.

В вашем примере использования правильным способом было бы изменить тип аргумента f() в const& поэтому нет такого адаптера не требуется, и использование const_cast добавить в кеш сохраненных вычислений.
Будьте уверены, чтобы не изменитьmutable члены на объявленном объекте const хоть.

Похоже, это работает во всех ваших случаях:

template <class T>
constexpr typename std::remove_reference<T>::type& copy(T&& t) {
    return t;
};

Это в точности как std::move, за исключением того, что вместо этого он возвращает ссылку на lvalue.

Другие вопросы по тегам