Есть ли приведение (или стандартная функция) с эффектом, противоположным `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 от вызова функции, для которого он предоставляет аргумент. Откуда у меня вопрос, можно ли это сделать без введения фиктивного имени:
- Есть ли способ (например, с использованием приведения) связать выражение rvalue типа A с модифицируемой ссылкой lvalue типа A& без введения именованного объекта типа A?
- Если нет, это сознательный выбор? (и если да, то почему?) Если есть, есть ли механизм, аналогичный
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.