Я ожидаю, что объект трассировки будет создан непосредственно на месте, без необходимости вызывать ctor перемещения.
Я не знаю, почему вы этого ожидаете.Пересылка делает именно это: перемещает или копирует 1) .В вашем примере вы создаете временное значение с trace()
, а затем переадресация перемещает его в x
. Если вы хотите создать объект T
на месте, то вам нужно передать аргументы для построенияT
, а не T
объект, который нужно переместить или скопировать.
Создайте конструктор на месте:
template <class... Args>
wrapper(std::in_place_t, Args&&... args)
:x{std::forward<Args>(args)...}
{}
И затем назовите его так:
wrapper<trace> w_3 {std::in_place};
// or if you need to construct an `trace` object with arguments;
wrapper<trace> w_3 {std::in_place, a1, a2, a3};
Направление комментария от ОП на другой ответ:
@ bolov Позволяет на минуту забыть о совершенной пересылке.Я думаю, что проблема заключается в том, что я хочу, чтобы объект был построен в конечном пункте назначения.Теперь, если его нет в конструкторе, теперь гарантируется, что это произойдет с гарантированным разрешением копирования / перемещения (здесь перемещение и копирование почти одинаковы).Чего я не понимаю, так это того, почему это не возможно в конструкторе.Мой тестовый пример доказывает, что этого не происходит в соответствии с текущим стандартом, но я не думаю, что это должно быть невозможно определить стандартом и реализовать компиляторами.Что мне не хватает, что такого особенного в ctor?
В ctor нет ничего особенного в этом отношении.Вы можете увидеть точно такое же поведение с помощью простой бесплатной функции:
template <class T>
auto simple_function(T&& a)
{
X x = std::forward<T>(a);
// ^ guaranteed copy or move (depending on what kind of argument is provided
}
auto test()
{
simple_function(X{});
}
Приведенный выше пример аналогичен вашему OP.Вы можете видеть simple_function
как аналог вашего конструктора оболочки, а мою локальную переменную x
как аналог вашего элемента данных в wrapper
.Механизм в этом отношении тот же.
Чтобы понять, почему вы не можете создать объект непосредственно в локальной области действия simple_function
(или как элемент данных в вашем объекте-оболочке в вашем случае), вынужно понять, как работает гарантированное копирование в C ++ 17 Я рекомендую этот отличный ответ .
Подводя итог этому ответу: в основном выражение prvalue не работаетматериализует объект, вместо этого он может инициализировать объект.Вы держите выражение как можно дольше, прежде чем использовать его для инициализации объекта (таким образом избегая некоторых копий / перемещений).Обратитесь к связанному ответу для более подробного, но дружественного объяснения.
В тот момент, когда ваше выражение используется для инициализации параметра simple_foo
(или параметра вашего конструктора), вы вынуждены материализовать объекти потерять выражение лица.Отныне у вас больше нет оригинального выражения prvalue, у вас есть созданный материализованный объект.И этот объект теперь нужно переместить в конечный пункт назначения - мой локальный x
(или ваш элемент данных x
).
Если мы немного изменим мой пример, мы увидим гарантированное разрешение копирования при работе:
auto simple_function(X a)
{
X x = a;
X x2 = std::move(a);
}
auto test()
{
simple_function(X{});
}
Без elision все будет выглядеть так:
X{}
создает временный объект в качестве аргумента для simple_function
.Давайте назовем это Temp1
Temp1
теперь перемещено (потому что это значение) в параметр a
из simple_function
a
копируется (потому что a
является lvalue) в x
a
перемещено (потому что std::move
приводит a
к xvalue) в x2
Теперь с C ++17 гарантированная копия elision
X{}
больше не материализует объект на месте.Вместо этого выражение сохраняется. - параметр
a
из simple_function
теперь можно инициализировать из выражения X{}
.Никакое копирование или перемещение не требуется и не требуется.
Остальное теперь то же самое:
a
скопировано в x1
a
перемещено в x2
Что вам нужно понять: как только вы что-то назвали, что-то должно существовать.Удивительно простая причина этого в том, что если у вас есть имя для чего-то, вы можете ссылаться на него несколько раз.Смотрите мой ответ на этот другой вопрос .Вы назвали параметр wrapper::wrapper
.Я назвал параметр simple_function
.В этот момент вы теряете выражение prvalue для инициализации этого именованного объекта.
Если вы хотите использовать гарантированное копирование в C ++ 17, и вам не нравится метод на месте, который вам нуженчтобы не называть вещи :) Вы можете сделать это с помощью лямбды.Идиома, которую я вижу чаще всего, в том числе в стандарте, - это способ на месте.Так как я не видел лямбда-путь в дикой природе, я не знаю, рекомендую ли я это.В любом случае, это так:
template<class T> class wrapper {
public:
template <class F>
wrapper(F initializer)
: x{initializer()}
{}
private:
T x;
};
auto test()
{
wrapper<X> w = [] { return X{};};
}
В C ++ 17 это не дает никаких копий и / или перемещений, и это работает, даже если X
удалил конструкторы копий и конструкторы перемещения.Объект будет построен в конечном пункте назначения, так же, как вы хотите.
1) Я говорю об идиоме переадресации при правильном использовании.std::forward
это просто актерский состав.