Фон
Я понимаю, что вопрос довольно громоздкий, поэтому постараюсь объяснить ситуацию в меру своих возможностей.
Начиная с C++20, агрегатную инициализацию шаблонных классов стало довольно легко использовать благодаря неявному выведению аргументов шаблона класса. Однако в большинстве реализаций, использующих это преимущество, создаются переменные-члены с явными именами.
В настоящее время я работаю с процессором, у которого есть неиспользуемый в противном случае регистр, и создаю крошечную библиотеку для взаимодействия с ним и остальной частью процессора; в идеале пользователи должны иметь возможность произвольно определять абстрактное содержимое регистра (например, указатель, четыре индекса uint8 и т. д.) без необходимости использовать имя переменной, навязанное им библиотекой.
В идеале пользователю не нужно было бы создавать объект, наследуемый от базового класса регистров, и он мог бы использовать агрегатную инициализацию.
Проблема
Следующая реализация C++23 функциональна, но не идеальна.
/* Library */
template <class UserInterpretation>
struct Register : public UserInterpretation {
void MoveToPhysicalRegister(void) {
u32& contents = *__builtin_bit_cast(u32*, this);
asm(...); // Processor-specific assembly to move to desired register
}
void MoveFromPhysicalRegister(void) {
asm(...); // Processor-specific assembly to move from desired register
}
};
Эта реализация работает нормально, когда конструктор по умолчанию вызывается перед любыми изменениями объекта посредством пользовательской интерпретации. С неявным выводом аргументов шаблона также работает построение объекта RegisterInterpretation на месте.
Однако попытка совокупной инициализации объекта регистра с использованием пользовательских переменных не удалась.
/* User */
struct RegisterInterpretation {
u32 InitMemAddr : 24;
u8 InitMode;
};
// Works
void UserFunctionDefaultConstructorFollowedByModifications(void) {
Register<RegisterInterpretation> userRegister;
userRegister.InitMemAddr = 0xFB9000;
userRegister.InitMode = 3;
userRegister.MoveToPhysicalRegister();
userRegister.InitMode = 2;
userRegister.MoveToPhysicalRegister();
}
// Works
void UserFunctionConstructionInPlace(void) {
Register userRegister = {RegisterInterpretation{.InitMemAddr = 0xFB9000, .InitMode = 3}};
userRegister.MoveToPhysicalRegister();
userRegister.InitMode = 2;
userRegister.MoveToPhysicalRegister();
user
}
// Fails
void UserFunctionAttemptedAggInitError(void) {
Register<RegisterInterpretation> userRegister = {.InitMemAddr = 0xFB9000, .InitMode = 0}; // ERROR
userRegister.MoveToPhysicalRegister();
userRegister.InitMode = 2;
userRegister.MoveToPhysicalRegister();
}
Сгенерированная ошибка
В сбойном экземпляре генерируются две ошибки одного типа.
Ошибка: указатель поля «InitMemAddr» не ссылается ни на одно поле типа «Регистр»
Ошибка: указатель поля «InitMode» не ссылается ни на одно поле типа «Регистр».
Обратите внимание, что один и тот же результат получается как с Clang, так и с GCC.
Вопросы
У меня есть два вопроса относительно наблюдаемого поведения:





Только прямые члены могут быть названы в списке назначенных инициализаторов.
[dcl.init.aggr]/(3.1), выделение мое:
Если список инициализаторов представляет собой список назначенных-инициализаторов, заключенный в фигурные скобки, агрегат должен иметь тип класса, идентификатор в каждом указателе должен называть прямой нестатический элемент данных класса, а явно инициализированные элементы агрегата имеют вид элементы, которые являются или содержат эти члены.
Примечание: эта структура странная.
InitModeникогда не будет гарантированно находиться в той же 32-битной ячейке памяти, что иInitMemAddr. Для этого он должен бытьu32 InitMode: 8;.