Ответ 1
TL; DR
Агрегатная инициализация может использоваться для продления срока службы временного, определяемый пользователем конструктор не может сделать то же самое, поскольку он эффективно выполняет вызов функции.
Примечание: как T const&
, так и T&&
применяются в случае инициализации агрегата и продления срока службы временных привязок к ним.
Что такое агрегат?
struct S { // (1)
std::vector<int>&& vec;
};
Чтобы ответить на этот вопрос, нам придется погрузиться в разницу между инициализацией агрегата и инициализацией типа класса, но сначала мы должны установить, что такое совокупность:
8.5.1p1
Агрегаты[dcl.init.aggr]
Агрегат - это массив или класс (раздел 9) без конструкторов, предоставляемых пользователем (12.1), без частных или защищенных нестатических элементов данных (раздел 11), без базовых классов (раздел 10) и без виртуальных функции (10.3)
Примечание. Вышеупомянутое означает, что (1) является агрегатом.
Как инициализируются агрегаты?
Инициализация между агрегатом и "неагрегатом" сильно отличается, здесь идет другой раздел прямо из стандарта:
8.5.1p2
Агрегаты[dcl.init.aggr]
Когда агрегат инициализируется списком инициализаторов, как указано в 8.5.4, элементы списка инициализаторов берутся как инициализаторы для членов агрегата, при увеличении индекса или порядка членов, Каждый элемент инициализируется копией из соответствующего предложения инициализатора.
В приведенной выше цитате указано, что мы инициализируем членов нашего агрегата с инициализаторами в предложении initializer, между ними нет шага.
struct A { std::string a; int b; };
A x { std::string {"abc"}, 2 };
Семантически приведенное выше эквивалентно инициализации наших членов с использованием приведенного ниже, просто A::a
и A::b
в этом случае доступны только через x.a
и x.b
.
std::string A::a { std::string {"abc"} };
int A::b { 2 };
Если мы изменим тип A::a
на rvalue-reference или const lvalue-reference, мы напрямую привяжем временное использование для инициализации к x.a
.
Правила rvalue-reference и const lvalue-ссылки говорят, что время жизни будет распространено на время жизни хоста, что и должно произойти.
Как инициализация с использованием конструктора, объявленного пользователем?
struct S { // (2)
std::vector<int>&& vec;
S(std::vector<int>&& v)
: vec{std::move(v)} // bind to the temporary provided
{ }
};
Конструктор - это не что иное, как причудливая функция, используемая для инициализации экземпляра класса. К ним применяются те же правила, которые применяются к функциям.
Когда дело доходит до продления срока жизни временных разниц, нет никакой разницы.
std::string&& func (std::string&& ref) {
return std::move (ref);
}
Временный, переданный в func
, не будет продлевать срок его службы только потому, что мы имеем аргумент, объявленный как rvalue/lvalue-reference. Даже если мы вернем "ту же" ссылку, чтобы она была доступна вне func
, этого просто не произойдет.
Это то, что происходит в конструкторе (2), ведь конструктор - это просто "причудливая функция", используемая для инициализации объекта.
12.2p5
Временные объекты[class.temporary]
Временная привязка ссылки или временный объект, являющийся полным объектом подобъекта, к которому привязана ссылка, сохраняется для времени жизни ссылки, за исключением:
Временная привязка к ссылочному элементу в конструкторе ctor-initializer (12.6.2) сохраняется до завершения конструктора.
Временная привязка к ссылочному параметру в вызове функции (5.2.2) сохраняется до завершения полного выражения, содержащего вызов.
Время жизни временной привязки к возвращаемому значению в операторе return функции (6.6.3) не распространяется; временное уничтожается в конце полного выражения в операторе return.
- Временная привязка к ссылке в new-initializer (5.3.4) сохраняется до завершения полного выражения, содержащего новый-инициализатор.
Примечание. Обратите внимание, что агрегатная инициализация с помощью new T { ... }
отличается от ранее упомянутых правил.