I want to represent the description of a persistent memory layout (e.g. Flash or EEPROM device) statically embedded in the program code (preferably in the ROM section), from a set of variadic template parameters, where the necessary offsets are automatically calculated at compile time.
The goal is to create an appropriate array initializer, that can be iterated at runtime, without the restrictions you'll get with std::get(std::tuple)
, which requires compile time indexing.
1st approach
I have created a simple data item descriptor class that binds a particular ID (should be provided as an enum type by the client), to the data layout (offset and size):
template
< typename ItemIdType
>
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<ItemIdType>& rhs)
: id(rhs.id)
, size(rhs.size)
, offset(rhs.offset)
{
}
};
Clients should use this class that binds to a particular data type and offset:
template
< typename DataType
, typename ItemIdType
>
struct DataItemDesc
: public DataItemDescBase<ItemIdType>
{
typedef DataType DataTypeSpec;
DataItemDesc(ItemIdType id_, std::size_t offset_ = 0)
: DataItemDescBase(id_,sizeof(DataTypeSpec),offset_)
{
}
DataItemDesc(const DataItemDesc<DataType,ItemIdType>& rhs)
: DataItemDescBase(rhs)
{
}
};
Finally I want to use a std::array
to store the concrete data layouts:
const std::array<DataItemDescBase<ItemIdType>,NumDataItems> dataItemDescriptors;
For the client I'd like to provide an array initializer from either a std::tuple
or a variadic template parameter list, thus the offsets of subsequent array elements are automatically calculated from offset + size of the previous element at compile time.
What currently works is that a client can use the following code to initialize the array:
namespace
{
static const std::array<DataItemDescBase<DataItemId::Values>,4> theDataLayout =
{ { DataItemDesc<int,DataItemId::Values>
( DataItemId::DataItem1 )
, DataItemDesc<short,DataItemId::Values>
( DataItemId::DataItem2
, sizeof(int))
, DataItemDesc<double,DataItemId::Values>
( DataItemId::DataItem3
, sizeof(int) + sizeof(short))
, DataItemDesc<char[10],DataItemId::Values>
( DataItemId::DataItem4
, sizeof(int) + sizeof(short) + sizeof(double))
} };
}
But letting the clients calculate the offsets manually looks error prone and tedious.
TL;DR; Is it posssible to calculate the offsets at compile time, and if yes can you give me a sketch how please?
2nd approach
I've tried the proposal from @Yakk's answer and just introduced a data aware base class for ProcessedEntry
like this:
template<typename Key>
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<Key>& rhs)
: id(rhs.id)
, offset(rhs.offset)
, size(rhs.size) {
}
};
template<typename Key, Key identifier, typename T, std::size_t Offset>
struct ProcessedEntry
: public ProcessedEntryBase<Key> {
ProcessedEntry()
: ProcessedEntryBase<Key>(identifier,Offset,sizeof(T)) {
}
};
I had intended to use a LayoutManager
base class that could be inherited and provided with the concrete layout from a constructor parameter:
template<typename Key, std::size_t NumEntries>
class LayoutManager {
public:
typedef std::array<ProcessedEntryBase<Key>,NumEntries> LayoutEntriesArray;
const LayoutEntriesArray& layoutEntries;
// ...
// methods to lookup particular entries by id
// ...
protected:
LayoutManager(LayoutEntriesArray layoutEntries_)
: layoutEntries(layoutEntries_) {
}
};
Client code
ConcreteLayout.hpp;
struct DataItemId {
enum Values {
DataItem1 ,
DataItem2 ,
DataItem3 ,
DataItem4 ,
};
};
class ConcretePersistentLayout
: public LayoutManager<DataItemId::Values,4> {
public:
ConcretePersistentLayout();
};
ConcreteLayout.cpp:
Layout< DataItemId::Values
, Entry< DataItemId::Values, DataItemId::DataItem1, int>
, Entry< DataItemId::Values, DataItemId::DataItem2, short >
, Entry< DataItemId::Values, DataItemId::DataItem3, double >
, Entry< DataItemId::Values, DataItemId::DataItem4, char[10] >
>::type theDataLayout; // using like this gives me a compile error,
// because I have no proper type 'prepend'
// I'd guess
}
ConcretePersistentLayout::ConcretePersistentLayout()
: LayoutManager<DataItemId::Values,4>(theDataLayout)
// ^^^^^^ Would this work to 'unpack' the tuple?
{
}
I want to loosely couple an accessor class with the LayoutManager
that that takes the id, calculates the persistent memory device address, fetches the data and casts to the data type bound to the key/id. I planned to let the client specify the key/data type bindings explicitly, thus static checks can be done on accessor functions.
Finally
I have something in production now, based on @Yakk's extended answer after that first round of asking for more clarification.
Also in regard to comments:
In this case I'm aware about the slicing problem, and it's guaranteed that the derived (template) classes stored in the
std::array<ProcessedEntryBase>
won't add any more data members or such. Functional binding (casting) is done separately.The indices trick proposed, also was a good hint about how to unpack variadic template parameters at runtime for indexed access, iterating.
DataItemDescBase<ItemIdType>
in a collection (likestd::array
) if you want to store inherited classes in it (likeDataItemDesc
). See e.g. What is the slicing problem or the Wikipedia article on object slicing. – Popularity