Background
I'm aware the question is quite the mouthfull, so I'll try to explain the situation to the best of my ability.
Aggregate initialization of templated classes has become quite easy to utilize with C++20 onwards due to implicit class template argument deduction. However, most implementations taking advantage of this create explicitly named member variables.
I am currently working with a processor that has an otherwise unused register and am creating a tiny library to interact with it and the rest of the processor; ideally, users should have the ability to arbitrarily define the abstract contents of the register (e.g., a pointer, four uint8 indices, etc.) without having to utilize a variable name forced upon them by the library.
Ideally, the user would not have to create an object that inherits from a base register class and would be able to utilize aggregate initialization.
Problem
The following C++23 implementation that is functional, but non-ideal.
/* 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
}
};
This implementation works fine when the default constructor is called before any modifications to the object via the user-defined interpretation. With implicit template argument deduction, constructing a RegisterInterpretation
object in place works as well.
However, attempting aggregate initialization of the register object using the user-defined variables fails.
/* 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();
}
Generated Error
In the failed instance, two errors of the same type are generated.
Error: field designator 'InitMemAddr' does not refer to any field in type 'Register'
Error: field designator 'InitMode' does not refer to any field in type 'Register'
Note that the same result is obtained with both Clang and GCC.
Questions
I have two questions regarding the observed behavior:
Only direct members can be named in a designated-initializer-list.
[dcl.init.aggr]/(3.1), emphasis mine:
If the initializer list is a brace-enclosed designated-initializer-list, the aggregate shall be of class type, the identifier in each designator shall name a direct non-static data member of the class, and the explicitly initialized elements of the aggregate are the elements that are, or contain, those members.