Ответ 1
Сначала: где std:: move и std:: forward определены?
См. 20.3
Компоненты Utility, <utility>
.
При реализации семантики перемещения источник предположительно остается в состоянии undefined. Должно ли это состояние быть действительным для объекта?
Очевидно, что объект все равно должен быть разрушительным. Но, кроме того, я считаю, что это хорошая идея, которую можно по-прежнему назначать. Стандарт говорит для объектов, которые удовлетворяют "MoveConstructible" и "MoveAssignable":
[Примечание: rv остается действительным объектом. Его состояние неуточнено. - конечная нота]
Это будет означать, я думаю, что объект все еще может участвовать в любой операции, которая не содержит никаких предварительных условий. Это включает CopyConstructible, CopyAssignable, Destructible и другие вещи. Обратите внимание, что это не потребует ничего для ваших собственных объектов с точки зрения основного языка. Требования выполняются только после того, как вы касаетесь стандартных компонентов библиотеки, которые определяют эти требования.
Далее: когда вы не заботитесь о семантике перемещения, существуют ли какие-либо ограничения, которые могут привести к тому, что неконстантная ссылка будет предпочтительнее ссылки rvalue при работе с параметрами функции?
Это, к сожалению, принципиально зависит от того, находится ли параметр в шаблоне функции и использует параметр шаблона:
void f(int const&); // takes all lvalues and const rvalues
void f(int&&); // can only accept nonconst rvalues
Однако для шаблона функции
template<typename T> void f(T const&);
template<typename T> void f(T&&);
Вы не можете этого сказать, потому что второй шаблон после вызова с lvalue имеет в качестве параметра синтезированного объявления тип U&
для nonconst lvalues (и лучше подходит) и U const&
для const lvalues (и быть неоднозначным). Насколько я знаю, нет частичного правила упорядочения, чтобы устранить эту вторую двусмысленность. Однако этот уже известен.
--
Изменить --
Несмотря на этот отчет о проблеме, я не думаю, что эти два шаблона неоднозначны. Частичное упорядочение сделает первый шаблон более специализированным, потому что после удаления ссылочных модификаторов и const
мы обнаружим, что оба типа одинаковы, а затем обратите внимание, что первый шаблон имел ссылку на const. В Стандарте говорится (14.9.2.4
)
Если для данного типа вывод преуспевает в обоих направлениях (т.е. типы идентичны после вышеперечисленных преобразований), и если тип из шаблона аргумента более качественный, чем тип из шаблона параметра ( как описано выше), этот тип считается более специализированным, чем другой.
Если для каждого рассматриваемого типа данный шаблон является, по меньшей мере, специализированным для всех типов и более специализированным для некоторого набора типов, а другой шаблон не является более специализированным для любых типов или не является, по меньшей мере, специализированным для любых типов, то данный шаблон более специализирован, чем другой шаблон.
Это делает шаблон T const&
победителем частичного упорядочения (и GCC действительно прав, чтобы выбрать его).
--
Изменить конец --
Что подводит меня к моему последнему вопросу. Вы по-прежнему не можете привязывать временные ссылки к неконстантным ссылкам. Но вы можете связать их с неконстантными ссылками rvalue.
Это хорошо объяснено в в этой статье. Второй вызов с использованием function2
принимает только неконкурентные значения. Остальная часть программы не заметит, если они будут изменены, потому что они больше не смогут получить доступ к этим rvalues! И 5
, который вы передаете, не является типом класса, поэтому создается скрытое временное и затем передается в ссылку int&&
rvalue. Код, вызывающий function2
, не сможет получить доступ к этому скрытому объекту здесь, поэтому он не заметит никаких изменений.
Другая ситуация заключается в том, что вы делаете это:
SomeComplexObject o;
function2(move(o));
Вы явно запросили, чтобы o
был перемещен, поэтому он будет изменен в соответствии со спецификацией перемещения. Однако перемещение - это логически не изменяющаяся операция (см. Статью). Это означает, что вы перемещаетесь или не должны быть видимыми из вызывающего кода:
SomeComplexObject o;
moveit(o); // #1
o = foo;
Если вы удалите строку, которая будет двигаться, поведение будет по-прежнему тем же, потому что оно все равно перезаписывается. Это, однако, означает, что код, который использует значение o
после того, как он был перенесен, плох, потому что он нарушает этот неявный контракт между moveit
и вызывающим кодом. Таким образом, в стандарте не указывается конкретное значение перемещенного из контейнера.