В чём причина того, что designated initializers в C++20 применимы только к агрегатам и требуют порядка объявления полей?
Именованные инициализаторы в C++20 предназначены для прямой инициализации полей агрегата, минуя конструктор. Они требуют порядка объявления, чтобы сохранить однозначную последовательную семантику и не превращать инициализацию в общий механизм вызова конструкторов с произвольным сопоставлением имён.
Для классов с пользовательской логикой конструирования этот механизм не применяется: там порядок и правила инициализации должны контролироваться конструкторами.
До C++20 агрегаты обычно инициализировали позиционно. Такой синтаксис краток, но плохо показывает назначение значений и делает код уязвимым к перестановке полей или ошибке в их порядке.
C++20 добавил designated initializers как более читаемый способ инициализации простых структур данных. Возможность ограничили агрегатами, чтобы не смешивать её с вызовом конструкторов и существующими правилами разрешения перегрузок.
Позиционная инициализация структуры с несколькими однотипными полями может быть неочевидной: компилятор проверит типы, но не намерение программиста. Именованная форма делает намерение явным, однако её ограничения важны при проектировании типов и публичных интерфейсов.
Нельзя произвольно переставлять designated initializers относительно объявления полей. Нельзя использовать их для приватных полей, для вызова конструктора или для обращения к унаследованным полям как к обычным обозначенным членам.
Агрегат — это тип, который удовлетворяет требованиям стандарта к агрегатной инициализации. В частности, наличие конструктора или других особенностей класса может лишить тип статуса агрегата; точный набор условий зависит от редакции стандарта. Designated initializer работает только с прямыми нестатическими членами агрегата.
Инициализация выполняется в порядке членов класса. Каждый явно указанный член должен идти после предыдущего указанного члена согласно этому порядку, а пропущенные члены инициализируются так же, как при обычной агрегатной инициализации: сначала используется значение по умолчанию, если оно задано, иначе применяется соответствующая инициализация по умолчанию.
Вызов конструктора здесь не происходит: объект агрегатно инициализируется напрямую. Запись вроде инициализации tls перед port будет отвергнута компилятором, даже если типы значений подходят.
Ограничение порядка снижает неоднозначность и сохраняет связь между синтаксисом и физическим порядком членов. Это также означает, что изменение порядка полей может сделать существующий исходный код некорректным; designated initializers не являются независимым от layout именованным API.
Если типу нужны инварианты, проверка аргументов, вычисление полей или запрет некоторых состояний, предпочтительнее конструктор. Designated initializers подходят для простых конфигураций и структур данных, где публичные поля являются частью намеренного интерфейса.
В библиотеке есть конфигурация сетевого сервера с портом, тайм-аутом и флагом TLS. Позиционная инициализация коротка, но плохо читается и становится опасной при добавлении нового поля. Конструктор делает проверку надёжной, однако требует проектирования параметров, перегрузок или отдельного типа параметров.
Команда выбирает агрегат с designated initializers для внутренней конфигурации, где допустимые значения проверяются отдельным этапом сборки конфигурации. Это даёт читаемые места вызова и позволяет добавлять поля с безопасными значениями по умолчанию, но порядок членов и публичность полей становятся частью поддерживаемого интерфейса.
Если же конфигурация экспортируется внешним пользователям и должна гарантировать, например, положительный тайм-аут или взаимную согласованность нескольких параметров, выбран был бы конструктор или фабрика. В этом случае потеря свободной агрегатной инициализации оправдана контролем инвариантов.
1. Можно ли смешивать позиционные и designated initializers?
Нет. В C++ designated initializer list не смешивается с обычными позиционными элементами. Если часть полей нужно задать явно, остальные можно пропустить, но явно указанные поля должны использовать designated-форму и идти в порядке объявления.
2. Разрешены ли designated initializers для класса с конструктором по умолчанию?
Сам факт наличия конструктора может нарушить требования к агрегату, поэтому нужно проверять статус конкретного типа по правилам применяемой редакции стандарта. Надёжный практический принцип таков: designated initializers предназначены для структур без пользовательской конструкторной логики; если типом управляет конструктор, используйте его интерфейс.
3. Инициализирует ли designated initializer унаследованный член?
Нет, он обозначает прямой нестатический член агрегата, а не произвольный член из иерархии. Даже если агрегат содержит базовый класс, его поля не становятся доступными через обозначение имени поля производного класса. Для такой модели следует использовать конструктор, фабрику или явную инициализацию базовой части.