Пересылка в агрегатный инициализатор?

Мне кажется, что агрегатная инициализация (подходящих типов) не считается конструктором, который вы можете вызвать (за исключением нескольких случаев).

Например, если у нас очень простой тип агрегата:

struct Foo {
    int x, y;
};

тогда это очевидно работает:

auto p = new Foo {42, 17};  // not "Foo (42, 17)" though...

но это не работает ни на одном из протестированных мной компиляторов (включая последние версии MSVC, GCC и Clang):

std::vector<Foo> v;
v.emplace_back(2, 3);

Опять же, мне кажется, что любой код, который хочет вызвать конструктор для типа T (в данном случае код в vector::emplace_back который пересылает переданные аргументы в c'or of T ,) не может просто использовать агрегатную инициализацию (кажется), потому что они используют скобки вместо фигурных скобок!

Это почему? Это просто пропущенная функция (никто еще не предложил/не реализовал ее) или есть более глубокие причины? Это немного странно, потому что агрегатные типы по определению не имеют другого конструктора, который делает разрешение неоднозначным, поэтому язык мог бы просто определить конструктор агрегатов по умолчанию (или что-то в этом роде), в котором все члены были бы аргументами по умолчанию.

Это просто вопрос синтаксиса? Если бы реализация vector::emplace_back в приведенном выше примере использовала размещение new с фигурными скобками вместо скобок, сработало бы это?

Примечание: я хочу поблагодарить те комментарии, которые указали на поведение vector и emplace потому что их комментарии будут полезны тем, кто найдет этот вопрос по этим ключевым словам, но я также хочу отметить, что это всего лишь примеры. Я выбрал самый знакомый и лаконичный пример, но моя точка зрения касалась явного вызова агрегатного инициализатора в любом коде (или, в частности, при placement new).

Ответы

Ответ 1

Для чего это стоит, P0960 "Разрешить инициализацию агрегатов из заключенного в скобки списка значений" делает именно то, что говорит. Кажется, он прошел EWG и находится на пути к C++ 20.

агрегатные типы по определению не имеют другого конструктора, делающего разрешение неоднозначным

Это неверно. Все классы имеют конструкторы по умолчанию, а также конструкторы копирования/перемещения. Даже если вы = delete их или они неявно удалены, у них все равно есть такие конструкторы (вы просто не можете их вызвать).

C++, будучи C++, естественно, есть угловые случаи, когда даже P0960 делает "неправильную вещь", как обрисовано в общих чертах в статье:

struct A;

struct C
{
  operator A(); //Implicitly convertible to 'A'
};

struct A { C c; }; //First member is a 'C'

C c2;
A a(c2);

Инициализация случай неоднозначности. a Две вещи могут произойти. Вы можете выполнить неявное преобразование c2 в A, а затем инициализировать a из полученного значения. Или вы могли бы выполнить совокупную инициализацию одним значения типа a C.

P0960 выбирает обратно совместимый маршрут: если конструктор может быть вызван (в соответствии с существующими правилами), он всегда имеет приоритет. Круглые скобки вызывают только агрегатную инициализацию, если нет конструктора, который мог бы быть вызван.

Ответ 2

https://en.cppreference.com/w/cpp/language/aggregate_initialization

Агрегированная инициализация не является конструктором.

Согласно этому документу, вы не определили никаких конструкторов и отвечаете другим условиям для Агрегированной инициализации. (Обратитесь к пункту "тип класса" в разделе "Объяснение"). Это означает, что ваш код не вызывает нечто вроде автоматически сгенерированного конструктора подписи Foo(int, int) но это просто еще одна функция.

В документе говорится о его эффекте:

Каждый элемент массива или нестатический член класса в порядке индекса/появления массива в определении класса инициализируется копией из соответствующего предложения списка инициализатора.

Так как vector::emplace_back(Args&&... args) работает таким образом, он не может найти такой конструктор.

Аргументы args... передаются в конструктор как std::forward<Args>(args)...

Так что он не находит такой конструктор.

Думая об этом, также имеет смысл, что ваш код не может скомпилировать auto p = new Foo (42, 17); ,

Еще один пример, если вы пишете конструктор любого типа (даже Foo::Foo() {}), auto p = new Foo {42, 17}; не работает. Потому что теперь он не соответствует условию агрегированной инициализации.

Насколько я знаю, Aggregate Initialization также работает в C, который даже не поддерживает конструкторы.

Здесь хорошая статья, которую стоит прочитать.