КОгда выбирается move-constructor?
От: DTF  
Дата: 06.07.17 18:47
Оценка:
Добрый вечер?

Короткий вопрос:
Где в стандарте перечислены условия, при которых для конструирования объекта выбирается move-конструктор?
Сам не нахожу


Тот же вопрос подробнее:
Вот есть код:
class TOnlyMovable {
public:
    TOnlyMovable() = default;
    TOnlyMovable(TOnlyMovable&&) {}
};

void foo(TOnlyMovable&& object) {
    TOnlyMovable m(object);
}


int main() {
    foo( TOnlyMovable() );
    return 0;
}


Он не компилируется, т.к. у TOnlyMovable удален копирующий конструктор, а перемещающий конструктор (в строке "TOnlyMovable m(object);") почему-то не вызывается.
Почему?
Казалось бы, несмотря на то, что object — это lvalue (я не ошибаюсь тут?), его тип — rvalue reference,
из чего можно сделатть вывод, что перемещать из этого объекта можно и нужно.
Re: КОгда выбирается move-constructor?
От: N. I.  
Дата: 06.07.17 19:28
Оценка:
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 такого же типа — можно:

void foo(TOnlyMovable&& object) {
    TOnlyMovable m(std::move(object));
}
Re: КОгда выбирается move-constructor?
От: prezident.mira Россия  
Дата: 06.07.17 22:14
Оценка:
DTF>Казалось бы, несмотря на то, что object — это lvalue (я не ошибаюсь тут?), его тип — rvalue reference,
DTF>из чего можно сделатть вывод, что перемещать из этого объекта можно и нужно.
Нельзя сделать такой вывод.

Тип и value category это две ортогональные сущности.
Отредактировано 06.07.2017 22:29 prezident.mira . Предыдущая версия .
Re[2]: КОгда выбирается move-constructor?
От: jazzer Россия Skype: enerjazzer
Дата: 14.07.17 05:51
Оценка: -2
Здравствуйте, N. I., Вы писали:

NI>
void foo(TOnlyMovable&& object) {
NI>    TOnlyMovable m(std::move(object));
NI>}


А зачем здесь std::move? object ведь уже rvalue-ref
jazzer (Skype: enerjazzer) Ночная тема для RSDN
Автор: jazzer
Дата: 26.11.09

You will always get what you always got
  If you always do  what you always did
Re[3]: КОгда выбирается move-constructor?
От: prezident.mira Россия  
Дата: 14.07.17 06:48
Оценка: +3
Здравствуйте, jazzer, Вы писали:

J>Здравствуйте, N. I., Вы писали:


NI>>
void foo(TOnlyMovable&& object) {
NI>>    TOnlyMovable m(std::move(object));
NI>>}


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 посмотрите внимательно.
Отредактировано 14.07.2017 7:07 prezident.mira . Предыдущая версия . Еще …
Отредактировано 14.07.2017 7:01 prezident.mira . Предыдущая версия .
Отредактировано 14.07.2017 7:00 prezident.mira . Предыдущая версия .
Отредактировано 14.07.2017 6:59 prezident.mira . Предыдущая версия .
Отредактировано 14.07.2017 6:57 prezident.mira . Предыдущая версия .
Отредактировано 14.07.2017 6:52 prezident.mira . Предыдущая версия .
Re[4]: КОгда выбирается move-constructor?
От: jazzer Россия Skype: enerjazzer
Дата: 14.07.17 08:07
Оценка:
Здравствуйте, prezident.mira, Вы писали:

PM>Здравствуйте, jazzer, Вы писали:


NI>>>
void foo(TOnlyMovable&& object) {
NI>>>    TOnlyMovable m(std::move(object));
NI>>>}


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 посмотрите внимательно.


У меня сейчас нет под рукой ни стандарта, ни компилятора.
Вот вижу такое на http://en.cppreference.com/w/cpp/language/value_category:

Even if the variable's type is rvalue reference, the expression consisting of its name is an lvalue expression

может и наврали, конечно.

А если не наврали, то получается, что object — это lvalue типа TOnlyMovable&&, а std::move(object) это xvalue того же самого типа TOnlyMovable&&.
jazzer (Skype: enerjazzer) Ночная тема для RSDN
Автор: jazzer
Дата: 26.11.09

You will always get what you always got
  If you always do  what you always did
Re[5]: КОгда выбирается move-constructor?
От: night beast СССР  
Дата: 14.07.17 08:15
Оценка:
Здравствуйте, jazzer, Вы писали:

J>У меня сейчас нет под рукой ни стандарта, ни компилятора.


ideone:
void foo(int&) { std::cout << "ref"; }

void foo(int&&) { std::cout << "rref"; }

int main() 
{
    int&& t = 1;
    foo(t);
    foo(1);
    return 0;
}
Re[5]: КОгда выбирается move-constructor?
От: prezident.mira Россия  
Дата: 14.07.17 08:17
Оценка:
Здравствуйте, 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.

AST dump можно посмотреть онлайн https://godbolt.org/g/mAbnmj.
Про object написано: 'class TOnlyMovable' lvalue.
Про std::move(object): 'class TOnlyMovable' xvalue

J>Вот вижу такое на http://en.cppreference.com/w/cpp/language/value_category:

А я вот вижу там такое: Each expression has some non-reference type.

J>

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
Отредактировано 14.07.2017 8:19 prezident.mira . Предыдущая версия .
Re[6]: КОгда выбирается move-constructor?
От: jazzer Россия Skype: enerjazzer
Дата: 14.07.17 10:05
Оценка: :)
Здравствуйте, night beast, Вы писали:

NB>Здравствуйте, jazzer, Вы писали:


J>>У меня сейчас нет под рукой ни стандарта, ни компилятора.


NB>ideone:

NB>
NB>void foo(int&) { std::cout << "ref"; }

NB>void foo(int&&) { std::cout << "rref"; }

NB>int main() 
NB>{
NB>    int&& t = 1;
NB>    foo(t);
NB>    foo(1);
NB>    return 0;
NB>}
NB>


Это про разрешение перегрузки, а не про тип. Ты с тем же успехом можешь передать массив в функцию, которая принимает указатель — но это же не будет означать, что тип массива — указатель.
тип t, очевидно — rvalue reference на int.
jazzer (Skype: enerjazzer) Ночная тема для RSDN
Автор: jazzer
Дата: 26.11.09

You will always get what you always got
  If you always do  what you always did
Re[7]: КОгда выбирается move-constructor?
От: night beast СССР  
Дата: 14.07.17 10:10
Оценка:
Здравствуйте, jazzer, Вы писали:

J>Это про разрешение перегрузки, а не про тип. Ты с тем же успехом можешь передать массив в функцию, которая принимает указатель — но это же не будет означать, что тип массива — указатель.

J>тип t, очевидно — rvalue reference на int.

это к вопросу о том, для чего нужен std::move
Re[8]: КОгда выбирается move-constructor?
От: jazzer Россия Skype: enerjazzer
Дата: 14.07.17 11:10
Оценка:
Здравствуйте, night beast, Вы писали:

NB>Здравствуйте, jazzer, Вы писали:


J>>Это про разрешение перегрузки, а не про тип. Ты с тем же успехом можешь передать массив в функцию, которая принимает указатель — но это же не будет означать, что тип массива — указатель.

J>>тип t, очевидно — rvalue reference на int.

NB>это к вопросу о том, для чего нужен std::move


так я же уже сказал: чтобы из lvalue типа TOnlyMovable&&, сделать xvalue того же самого типа TOnlyMovable&&.

вот еще помедитировать:
https://ideone.com/BLvcmc
jazzer (Skype: enerjazzer) Ночная тема для RSDN
Автор: jazzer
Дата: 26.11.09

You will always get what you always got
  If you always do  what you always did
Re[9]: КОгда выбирается move-constructor?
От: night beast СССР  
Дата: 14.07.17 11:21
Оценка: +1
Здравствуйте, jazzer, Вы писали:

NB>>это к вопросу о том, для чего нужен std::move


J>так я же уже сказал: чтобы из lvalue типа TOnlyMovable&&, сделать xvalue того же самого типа TOnlyMovable&&.


не пойму я тебя. здесь
void foo(TOnlyMovable&& object) {
    TOnlyMovable m(std::move(object));
}

std::move нужен? если не нужен, то какой по твоему вызовется конструктор?
Re[6]: КОгда выбирается move-constructor?
От: jazzer Россия Skype: enerjazzer
Дата: 14.07.17 11:23
Оценка:
Здравствуйте, 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>>Вот вижу такое на http://en.cppreference.com/w/cpp/language/value_category:

PM>А я вот вижу там такое: Each expression has some non-reference type.

хз что это должно означать, если честно.

J>>

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

объясни тогда, плиз, единички вот здесь: https://ideone.com/BLvcmc
jazzer (Skype: enerjazzer) Ночная тема для RSDN
Автор: jazzer
Дата: 26.11.09

You will always get what you always got
  If you always do  what you always did
Re[10]: КОгда выбирается move-constructor?
От: jazzer Россия Skype: enerjazzer
Дата: 14.07.17 11:48
Оценка:
Здравствуйте, night beast, Вы писали:

NB>Здравствуйте, jazzer, Вы писали:


NB>>>это к вопросу о том, для чего нужен std::move


J>>так я же уже сказал: чтобы из lvalue типа TOnlyMovable&&, сделать xvalue того же самого типа TOnlyMovable&&.


NB>не пойму я тебя. здесь

NB>
NB>void foo(TOnlyMovable&& object) {
NB>    TOnlyMovable m(std::move(object));
NB>}
NB>

NB>std::move нужен? если не нужен, то какой по твоему вызовется конструктор?

нужен, чтоб lvalue типа TOnlyMovable&& превратить в xvalue того же самого типа TOnlyMovable&& — тогда из перегрузке будет выбран конструктор перемещения.
Тип rvalue reference и там, и там, разница в lvalue/xvalue.
т.е. lvalue типа TOnlyMovable&& свяжется с аргументом типа TOnlyMovable&,
а xvalue типа TOnlyMovable&& свяжется с аргументом типа TOnlyMovable&&

Ты посмотри на мой пример в ideone, там есть над чем подумать.
jazzer (Skype: enerjazzer) Ночная тема для RSDN
Автор: jazzer
Дата: 26.11.09

You will always get what you always got
  If you always do  what you always did
Re[7]: КОгда выбирается move-constructor?
От: prezident.mira Россия  
Дата: 14.07.17 11:51
Оценка:
Здравствуйте, 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


Как понимать "тогда"?
Отредактировано 14.07.2017 13:50 prezident.mira . Предыдущая версия . Еще …
Отредактировано 14.07.2017 12:11 prezident.mira . Предыдущая версия .
Re[11]: КОгда выбирается move-constructor?
От: night beast СССР  
Дата: 14.07.17 12:08
Оценка: +1
Здравствуйте, 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.

Отредактировано 14.07.2017 12:18 night beast . Предыдущая версия .
Re[3]: КОгда выбирается move-constructor?
От: N. I.  
Дата: 15.07.17 11:32
Оценка: +1
jazzer:

NI>>
void foo(TOnlyMovable&& object) {
NI>>    TOnlyMovable m(std::move(object));
NI>>}


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.
Отредактировано 15.07.2017 12:58 N. I. . Предыдущая версия . Еще …
Отредактировано 15.07.2017 12:57 N. I. . Предыдущая версия .
Отредактировано 15.07.2017 11:34 N. I. . Предыдущая версия .
Re[4]: КОгда выбирается move-constructor?
От: prezident.mira Россия  
Дата: 15.07.17 12:29
Оценка:
Здравствуйте, 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-тип выражения, насколько я знаю, ненаблюдаем нигде.
Re[5]: КОгда выбирается move-constructor?
От: N. I.  
Дата: 15.07.17 13:05
Оценка:
Здравствуйте, prezident.mira, Вы писали:

NI>> тип выражения — decltype((object)).


PM>Но ведь (object) это lvalue типа TOnlyMovable, а для lvalue типа T, тип, обозначаемый decltype, является T&. В данном случае — TOnlyMovable&.


Поправил.

PM>Но, на мой взгляд, проще в тех правилах сразу говорить, что типом выражения с идентификатором или вызовом функции будет тип идентификатора или тип возвращаемого значения, но избавленный от "ссылочности".


Стандартизаторы C++ любят хитрые манёвры, когда смысл сказанного в одной части стандарта внезапно преображается во что-то иное после прочтения другой.
Re: КОгда выбирается move-constructor?
От: placement_new  
Дата: 06.08.17 15:18
Оценка:
Здравствуйте, DTF, Вы писали:

DTF>Добрый вечер?


Вам не кажется что у такого С++ нет будущего?
Представте себя на месте новичка: он видит параметр object имеет тип TOnlyMovable&&, но std::move (который на самом деле просто static_cast<TOnlyMovable&&> к тому же самому типу) все кардинально меняет. Это же логически неверно...
Я как бы понимаю почему так было сделано...
http://thbecker.net/articles/rvalue_references/section_05.html

Но это выглядит как костыль на костыле...
Отредактировано 06.08.2017 15:24 placement_new . Предыдущая версия . Еще …
Отредактировано 06.08.2017 15:19 placement_new . Предыдущая версия .
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.