Он не компилируется, т.к. у TOnlyMovable удален копирующий конструктор, а перемещающий конструктор (в строке "TOnlyMovable m(object);") почему-то не вызывается.
Почему?
Казалось бы, несмотря на то, что object — это lvalue (я не ошибаюсь тут?), его тип — rvalue reference,
из чего можно сделатть вывод, что перемещать из этого объекта можно и нужно.
DTF:
DTF> Где в стандарте перечислены условия, при которых для конструирования объекта выбирается move-конструктор?
Если я ничего не забыл, то за исключением специальных случаев, описанных в [class.copy.elision], выбор делается по тем же правилам разрешения перегрузки, что и для обычных функций с rvalue reference параметром. Кандидаты формируются по [over.match.ctor] или [over.match.copy] в зависимости от контекста инициализации — см. [dcl.init].
DTF>Он не компилируется, т.к. у TOnlyMovable удален копирующий конструктор, а перемещающий конструктор (в строке "TOnlyMovable m(object);") почему-то не вызывается. DTF>Почему? DTF>Казалось бы, несмотря на то, что object — это lvalue
Вот потому и не компилируется. Чтобы проинициализировать rvalue ссылку перемещающего конструктора TOnlyMovable, lvalue типа TOnlyMovable в качестве инициализатора использовать нельзя — см. [dcl.init.ref]. А вот rvalue такого же типа — можно:
DTF>Казалось бы, несмотря на то, что object — это lvalue (я не ошибаюсь тут?), его тип — rvalue reference, DTF>из чего можно сделатть вывод, что перемещать из этого объекта можно и нужно.
Нельзя сделать такой вывод.
Тип и value category это две ортогональные сущности.
J>А зачем здесь std::move? object ведь уже rvalue-ref
rvalue-ref это тип, а не категория значения rvalue.
В строке
TOnlyMovable m(std::move(object))
подвыражение object является lvalue типа TOnlyMovable, поэтому move-constructor вызван быть не может, если написать
TOnlyMovable m(object)
т.к. тип у параметра move-constructor-а это rvalue-reference, которое к lvalue не биндится.
А вот подвыражение std::move(object) уже является xvalue типа TOnlyMovable и биндится к rvalue-reference.
P.S. прежде чем писать мне сообщения (или лепить минусы) про то, что типом будет не TOnlyMovable, а TOnlyMovable&& — почитайте стандарт или хотя бы на дамп AST в clang посмотрите внимательно.
J>>А зачем здесь std::move? object ведь уже rvalue-ref
PM>rvalue-ref это тип, а не категория значения rvalue.
Я и не говорил, что это категория.
PM>А вот подвыражение std::move(object) уже является xvalue типа TOnlyMovable и биндится к rvalue-reference.
std::move(object) возвращает rvalue reference:
template< class T > constexpr typename std::remove_reference<T>::type&& move( T&& t ) noexcept;
PM>P.S. прежде чем писать мне сообщения (или лепить минусы) про то, что типом будет не TOnlyMovable, а TOnlyMovable&& — почитайте стандарт или хотя бы на дамп AST в clang посмотрите внимательно.
Здравствуйте, jazzer, Вы писали:
PM>>А вот подвыражение std::move(object) уже является xvalue типа TOnlyMovable и биндится к rvalue-reference.
J>std::move(object) возвращает rvalue reference: J>template< class T > constexpr typename std::remove_reference<T>::type&& move( T&& t ) noexcept;
А я и не говорил, что тип возвращаемого значения std::move не ссылка
Но тип возвращаемого значения функции и тип [под]выражения, состоящего из вызова этой функции — это не одно и то же!!!
PM>>P.S. прежде чем писать мне сообщения (или лепить минусы) про то, что типом будет не TOnlyMovable, а TOnlyMovable&& — почитайте стандарт или хотя бы на дамп AST в clang посмотрите внимательно.
J>У меня сейчас нет под рукой ни стандарта, ни компилятора.
http://eel.is/c++draft/expr#5
If an expression initially has the type “reference to T” ([dcl.ref], [dcl.init.ref]), the type is adjusted to T prior to any further analysis.
J>Even if the variable's type is rvalue reference, the expression consisting of its name is an lvalue expression
J>может и наврали, конечно.
Нет, не наврали.
J>А если не наврали, то получается, что object — это lvalue типа TOnlyMovable&&, а std::move(object) это xvalue того же самого типа TOnlyMovable&&.
Только типа TOnlyMovable
Это про разрешение перегрузки, а не про тип. Ты с тем же успехом можешь передать массив в функцию, которая принимает указатель — но это же не будет означать, что тип массива — указатель.
тип t, очевидно — rvalue reference на int.
Здравствуйте, jazzer, Вы писали:
J>Это про разрешение перегрузки, а не про тип. Ты с тем же успехом можешь передать массив в функцию, которая принимает указатель — но это же не будет означать, что тип массива — указатель. J>тип t, очевидно — rvalue reference на int.
Здравствуйте, night beast, Вы писали:
NB>Здравствуйте, jazzer, Вы писали:
J>>Это про разрешение перегрузки, а не про тип. Ты с тем же успехом можешь передать массив в функцию, которая принимает указатель — но это же не будет означать, что тип массива — указатель. J>>тип t, очевидно — rvalue reference на int.
NB>это к вопросу о том, для чего нужен std::move
так я же уже сказал: чтобы из lvalue типа TOnlyMovable&&, сделать xvalue того же самого типа TOnlyMovable&&.
Здравствуйте, jazzer, Вы писали:
NB>>это к вопросу о том, для чего нужен std::move
J>так я же уже сказал: чтобы из lvalue типа TOnlyMovable&&, сделать xvalue того же самого типа TOnlyMovable&&.
Здравствуйте, prezident.mira, Вы писали:
PM>Но тип возвращаемого значения функции и тип [под]выражения, состоящего из вызова этой функции — это не одно и то же!!!
Хотелось бы увидеть обоснование этому утверждению.
PM>http://eel.is/c++draft/expr#5 PM>If an expression initially has the type “reference to T” ([dcl.ref], [dcl.init.ref]), the type is adjusted to T prior to any further analysis.
Это к чему?
PM>AST dump можно посмотреть онлайн https://godbolt.org/g/mAbnmj. PM>Про object написано: 'class TOnlyMovable' lvalue. PM>Про std::move(object): 'class TOnlyMovable' xvalue
J>>Even if the variable's type is rvalue reference, the expression consisting of its name is an lvalue expression
J>>может и наврали, конечно. PM>Нет, не наврали.
J>>А если не наврали, то получается, что object — это lvalue типа TOnlyMovable&&, а std::move(object) это xvalue того же самого типа TOnlyMovable&&. PM>Только типа TOnlyMovable
Здравствуйте, night beast, Вы писали:
NB>Здравствуйте, jazzer, Вы писали:
NB>>>это к вопросу о том, для чего нужен std::move
J>>так я же уже сказал: чтобы из lvalue типа TOnlyMovable&&, сделать xvalue того же самого типа TOnlyMovable&&.
NB>не пойму я тебя. здесь NB>
NB>std::move нужен? если не нужен, то какой по твоему вызовется конструктор?
нужен, чтоб lvalue типа TOnlyMovable&& превратить в xvalue того же самого типа TOnlyMovable&& — тогда из перегрузке будет выбран конструктор перемещения.
Тип rvalue reference и там, и там, разница в lvalue/xvalue.
т.е. lvalue типа TOnlyMovable&& свяжется с аргументом типа TOnlyMovable&,
а xvalue типа TOnlyMovable&& свяжется с аргументом типа TOnlyMovable&&
Ты посмотри на мой пример в ideone, там есть над чем подумать.
Здравствуйте, jazzer, Вы писали:
J>Здравствуйте, prezident.mira, Вы писали:
PM>>Но тип возвращаемого значения функции и тип [под]выражения, состоящего из вызова этой функции — это не одно и то же!!!
J>Хотелось бы увидеть обоснование этому утверждению.
Обоснование крайне простое: я не знаю, где стандарт явно гарантирует, что тип выражения с вызовом функции совпадает с типом возвращаемого значения функции.
Но зато я знаю место в стандарте, где это не так. Т.е. про выражение, состоящего из вызова функции с возвращаемым типом T&, говорится, что тип у выражения T.
UPD: на самом деле такое место есть. Да, говорится, что совпадает. Но "If an expression initially has the type “reference to T” ([dcl.ref], [dcl.init.ref]), the type is adjusted to T prior to any further analysis.". Что такое initially и prior to any further analysis не определено, но общий посыл таков, что reference-тип выражений "ненаблюдаем".
PM>>AST dump можно посмотреть онлайн https://godbolt.org/g/mAbnmj. PM>>Про object написано: 'class TOnlyMovable' lvalue. PM>>Про std::move(object): 'class TOnlyMovable' xvalue
J>Это детали реализации конкретного компилятора, не более того.
Как минимум его поведение здесь не противоречит вышеупомянутому мной месту в стандарте. И вообще clang старается крайне строго следовать стандарту.
J>>>А если не наврали, то получается, что object — это lvalue типа TOnlyMovable&&, а std::move(object) это xvalue того же самого типа TOnlyMovable&&. PM>>Только типа TOnlyMovable
J>объясни тогда, плиз, единички вот здесь: https://ideone.com/BLvcmc
Здравствуйте, jazzer, Вы писали:
NB>>std::move нужен? если не нужен, то какой по твоему вызовется конструктор?
J>нужен,
воот. о чем и речь.
J>чтоб lvalue типа TOnlyMovable&& превратить в xvalue того же самого типа TOnlyMovable&& — тогда из перегрузке будет выбран конструктор перемещения. J>Тип rvalue reference и там, и там, разница в lvalue/xvalue.
Named rvalue references are lvalues. Unnamed rvalue references are rvalues. This is important to understand why the std::move call is necessary in: foo&& r = foo(); foo f = std::move(r);
из стандарта:
In general, the effect of this rule is that named rvalue references are treated as lvalues and unnamed rvalue
references to objects are treated as xvalues; rvalue references to functions are treated as lvalues whether
named or not.
J>А зачем здесь std::move? object ведь уже rvalue-ref
Тип переменной (в частности, параметра функции) может не совпадать с типом выражения, образованного этой переменной. В данном случае тип параметра object — TOnlyMovable&&, но тип выражения object — TOnlyMovable. Кстати, оба типа можно запросить через decltype: тип параметра будет эквивалентен decltype(object), а тип выражения — std::remove_reference_t<decltype((object))>.
В контексте инициализации ссылки во внимание принимаются тип и value category выражения, а какой там declared тип у параметра, используемого в качестве выражения, имеет значение лишь в той мере, в которой это необходимо для определения типа выражения. Type & value category выражения object в данном случае определяются согласно C++14 [expr.prim.general] / 8
An identifier is an id-expression provided it has been suitably declared (Clause 7). [ Note: for operator-function-ids, see 13.5; for conversion-function-ids, see 12.3.2; for literal-operator-ids, see 13.5.8; for templateids, see 14.2. A class-name or decltype-specifier prefixed by ~ denotes a destructor; see 12.4. Within the definition of a non-static member function, an identifier that names a non-static member is transformed to a class member access expression (9.3.1). —end note ] The type of the expression is the type of the identifier. The result is the entity denoted by the identifier. The result is an lvalue if the entity is a function, variable, or data member and a prvalue otherwise.
и C++14 [expr] / 5
If an expression initially has the type “reference to T” (8.3.2, 8.5.3), the type is adjusted to T prior to any further analysis. The expression designates the object or function denoted by the reference, and the expression is an lvalue or an xvalue, depending on the expression.
Тип выражения может быть ссылочным только на первом этапе в процессе определения "настоящего" типа выражения, к которому затем применяются остальные правила. Выражение object — это lvalue типа TOnlyMovable. В отношении std::move(object) действуют правила для function call:
C++14 [expr.call] / 3:
If the postfix-expression designates a destructor (12.4), the type of the function call expression is void; otherwise, the type of the function call expression is the return type of the statically chosen function (i.e., ignoring the virtual keyword), even if the type of the function actually called is different. This return type shall be an object type, a reference type or cv void.
(ссылочность опять же удаляется в соответствии с C++14 [expr] / 5)
C++14 [expr.call] / 10:
A function call is an lvalue if the result type is an lvalue reference type or an rvalue reference to function type, an xvalue if the result type is an rvalue reference to object type, and a prvalue otherwise.
Выражение std::move(object) — это xvalue типа TOnlyMovable.
Здравствуйте, N. I., Вы писали:
NI> тип выражения — decltype((object)).
Но ведь (object) это lvalue типа TOnlyMovable, а для lvalue типа T, тип, обозначаемый decltype, является T&. В данном случае — TOnlyMovable&.
Как понимать тогда Ваши процитированные слова? Ведь decltype дал тип, который не совпадает ни с типом, которое выражение имело initially (TOnlyMovable&&), ни с TOnlyMovable, как этого требует пункт про удаление ссылочности у типа выражения.
NI>
If an expression initially has the type “reference to T” (8.3.2, 8.5.3), the type is adjusted to T prior to any further analysis. The expression designates the object or function denoted by the reference, and the expression is an lvalue or an xvalue, depending on the expression.
Довольно мерзенькое правило. Непонятно, что значит initially. Т.е. да, есть несколько пунктов, два из которых Вы процитировали, которые говорят о том, что типом выражения может быть ссылка и это правило снимает ссылочность. Но, на мой взгляд, проще в тех правилах сразу говорить, что типом выражения с идентификатором или вызовом функции будет тип идентификатора или тип возвращаемого значения, но избавленный от "ссылочности".
initial-тип выражения, насколько я знаю, ненаблюдаем нигде.
Здравствуйте, prezident.mira, Вы писали:
NI>> тип выражения — decltype((object)).
PM>Но ведь (object) это lvalue типа TOnlyMovable, а для lvalue типа T, тип, обозначаемый decltype, является T&. В данном случае — TOnlyMovable&.
Поправил.
PM>Но, на мой взгляд, проще в тех правилах сразу говорить, что типом выражения с идентификатором или вызовом функции будет тип идентификатора или тип возвращаемого значения, но избавленный от "ссылочности".
Стандартизаторы C++ любят хитрые манёвры, когда смысл сказанного в одной части стандарта внезапно преображается во что-то иное после прочтения другой.
Вам не кажется что у такого С++ нет будущего?
Представте себя на месте новичка: он видит параметр object имеет тип TOnlyMovable&&, но std::move (который на самом деле просто static_cast<TOnlyMovable&&> к тому же самому типу) все кардинально меняет. Это же логически неверно...
Я как бы понимаю почему так было сделано... http://thbecker.net/articles/rvalue_references/section_05.html