Crear un inicializador de matriz a partir de una tupla o parámetros de plantilla variadic

Quiero representar la descripción de una disposición de memoria persistente (por ejemplo, dispositivo Flash o EEPROM) incorporada estáticamente en el código del progtwig (preferiblemente en la sección ROM), a partir de un conjunto de parámetros de plantilla variados, donde los desplazamientos necesarios se calculan automáticamente en tiempo de comstackción .

El objective es crear un inicializador de matriz apropiado, que pueda iterarse en tiempo de ejecución, sin las restricciones que obtendrá con std::get(std::tuple) , que requiere indexación en tiempo de comstackción.


Primer acercamiento

Creé una clase de descriptor de elemento de datos simple que vincula un ID particular (debe ser proporcionado como un tipo de enumeración por el cliente), al diseño de datos (desplazamiento y tamaño):

 template  struct DataItemDescBase { const ItemIdType id; const std::size_t size; const std::size_t offset; DataItemDescBase(ItemIdType id_, std::size_t size_, std::size_t offset_) : id(id_) , size(size_) , offset(offset_) { } DataItemDescBase(const DataItemDescBase& rhs) : id(rhs.id) , size(rhs.size) , offset(rhs.offset) { } }; 

Los clientes deben usar esta clase que se une a un tipo de datos y un desplazamiento en particular:

 template  struct DataItemDesc : public DataItemDescBase { typedef DataType DataTypeSpec; DataItemDesc(ItemIdType id_, std::size_t offset_ = 0) : DataItemDescBase(id_,sizeof(DataTypeSpec),offset_) { } DataItemDesc(const DataItemDesc& rhs) : DataItemDescBase(rhs) { } }; 

Finalmente, quiero usar std::array para almacenar los diseños de datos concretos:

 const std::array<DataItemDescBase,NumDataItems> dataItemDescriptors; 

Para el cliente, me gustaría proporcionar un inicializador de matriz desde una lista std::tuple o una lista de parámetros de plantilla variadic, por lo que los desplazamientos de los elementos de matriz subsiguientes se calculan automáticamente a partir del desplazamiento + tamaño del elemento anterior en tiempo de comstackción.

Lo que actualmente funciona es que un cliente puede usar el siguiente código para inicializar la matriz:

 namespace { static const std::array<DataItemDescBase,4> theDataLayout = { { DataItemDesc ( DataItemId::DataItem1 ) , DataItemDesc ( DataItemId::DataItem2 , sizeof(int)) , DataItemDesc ( DataItemId::DataItem3 , sizeof(int) + sizeof(short)) , DataItemDesc ( DataItemId::DataItem4 , sizeof(int) + sizeof(short) + sizeof(double)) } }; } 

Pero dejar que los clientes calculen las compensaciones de forma manual parece propenso a errores y tedioso.

TL; DR; ¿Es posible calcular las compensaciones en tiempo de comstackción, y si es así, puede darme un boceto, por favor?


Segundo enfoque

Probé la propuesta de la respuesta de @ Yakk y acabo de presentar una clase base de Data Process para ProcessedEntry como esta:

 template struct ProcessedEntryBase { const Key id; const std::size_t offset; const std::size_t size; ProcessedEntryBase(Key id_ = Key(), std::size_t offset_ = 0, std::size_t size_ = 0) : id(id_) , offset(offset_) , size(size_) { } ProcessedEntryBase(const ProcessedEntryBase& rhs) : id(rhs.id) , offset(rhs.offset) , size(rhs.size) { } }; template struct ProcessedEntry : public ProcessedEntryBase { ProcessedEntry() : ProcessedEntryBase(identifier,Offset,sizeof(T)) { } }; 

Tenía la intención de utilizar una clase base de LayoutManager que podría heredarse y proporcionarse con el diseño concreto de un parámetro constructor:

 template class LayoutManager { public: typedef std::array<ProcessedEntryBase,NumEntries> LayoutEntriesArray; const LayoutEntriesArray& layoutEntries; // ... // methods to lookup particular entries by id // ... protected: LayoutManager(LayoutEntriesArray layoutEntries_) : layoutEntries(layoutEntries_) { } }; 

Codigo del cliente

ConcreteLayout.hpp;

 struct DataItemId { enum Values { DataItem1 , DataItem2 , DataItem3 , DataItem4 , }; }; class ConcretePersistentLayout : public LayoutManager { public: ConcretePersistentLayout(); }; 

ConcreteLayout.cpp:

 Layout< DataItemId::Values , Entry , Entry , Entry , Entry >::type theDataLayout; // using like this gives me a compile error, // because I have no proper type 'prepend' // I'd guess } ConcretePersistentLayout::ConcretePersistentLayout() : LayoutManager(theDataLayout) // ^^^^^^ Would this work to 'unpack' the tuple? { } 

Quiero LayoutManager libremente una clase de acceso con LayoutManager que toma la identificación, calcula la dirección del dispositivo de memoria persistente, busca los datos y los envía al tipo de datos vinculado a la clave / id. Planeé dejar que el cliente especificara los enlaces de clave / tipo de datos explícitamente, así se pueden hacer comprobaciones estáticas en las funciones de acceso.


Finalmente

Tengo algo en producción ahora, basado en la respuesta extendida de @Yakk después de esa primera ronda en la que pedí más aclaraciones.


También con respecto a los comentarios:

  1. En este caso, conozco el problema del corte y se garantiza que las clases derivadas (plantilla) almacenadas en std::array no agregarán más miembros de datos o cosas por el estilo. La unión funcional (fundición) se realiza por separado.

  2. El truco de los índices propuesto, también fue una buena pista sobre cómo descomprimir los parámetros de plantilla variadic en tiempo de ejecución para el acceso indexado, iterando.

    Para que se produzca la acumulación de tiempo de comstackción, debe tener una secuencia de tiempo de comstackción.

    Una manera fácil de hacer esto sería usar plantillas variadic. Cada entrada sería un identificador y un tamaño de un elemento particular, o el identificador y el tipo de un elemento en particular.

    El paquete de entradas de nivel superior sería un Layout :

     template struct LayoutHelper { typedef std::tuple<> type; }; template struct Layout:LayoutHelper<0, Key, Entries...> {}; 

    Cada entrada sería:

     template struct Entry {}; 

    Entonces, hacemos algo como esto:

     template struct ProcessedEntry {}; template struct LayoutHelper, Entries...> { typedef typename prepend < ProcessedEntry< Key, id0, D0, offset > , typename LayoutHelper::type >::type type; }; 

    El uso se vería así:

     Layout< FooEnum, Entry< FooEnum, eFoo, char[10] >, Entry< FooEnum, eFoo2, double > > layout; 

    que, después de escribir o encontrar un prepend que tome un elemento y una tuple , y antes de que el elemento esté al frente, significaría que Layout::type contendría una tuple que describe el diseño de tus datos.

     template struct prepend; templateclass Pack, typename... Ts> struct prepend> { typedef Pack type; }; // use: prepend::type is std::tuple // this removes some ::type and typename boilerplate, if it works in your compiler: template using Prepend = typename prepend::type; 

    A continuación, descomprimir esa tuple en una std::array si lo desea. Utilizaría el truco de índices para hacer esto (hay muchos ejemplos en desbordamiento de stack que usan este mismo truco de diferentes maneras).

    O bien, podría tomar su ProcessedEntry y agregar métodos para acceder a los datos, luego escribir un progtwig de búsqueda de Key que recorra la tuple , buscar la Key correspondiente y luego devolver el offset y el size (o incluso el tipo) como tiempo de comstackción código. Tal vez tome una array como argumento y realice el reintepret_cast , devolviendo una referencia a los data .

    Eliminar el FooEnum repetido sería bueno mediante el using alias.