从元组或variables模板参数创build数组初始值设定项

我想从一组可变模板参数中静态embedded程序代码(最好在ROM部分)中描述持久化存储器布局(例如Flash或EEPROM器件),其中必要的偏移量在编译时自动计算。

目标是创build一个合适的数组初始值设定项,它可以在运行时迭代,而不受std::get(std::tuple)获得的限制,这需要编译时间索引。


第一种方法

我已经创build了一个简单的数据项描述符类,它绑定了一个特定的ID(应该作为客户端的枚举types提供)到数据布局(offset和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) { } }; 

客户端应该使用绑定到特定数据types和偏移量的类:

 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) { } }; 

最后,我想使用一个std::array来存储具体的数据布局:

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

对于客户端来说,我希望从std::tuple或variadic模板参数列表中提供一个数组初始值设定项,因此后续数组元素的偏移量将在编译时根据前一个元素的offset + size自动计算。

目前工作的是客户端可以使用下面的代码来初始化数组:

 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)) } }; } 

但是让客户手动计算偏移量看起来很容易出错,也很麻烦。

TL; DR; 在编译时计算偏移量是否可行?如果是的话,你可以给我一个草图吗?


第二种方法

我已经从@Yakk的回答中尝试了这个build议,并且像这样为ProcessedEntry引入了一个数据感知基类:

 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)) { } }; 

我打算使用一个可以inheritance的LayoutManager基类,并从构造函数参数中提供具体的布局:

 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_) { } }; 

客户端代码

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? { } 

我想松散地耦合一个访问器类与LayoutManager ,它采用ID,计算持久性内存设备地址,获取数据,并强制绑定到键/ ID的数据types。 我打算让客户明确指定键/数据types绑定,因此可以对访问函数进行静态检查。


最后

在第一轮要求进一步澄清之后,基于@雅克的扩展答案 ,我现在正在制作一些东西。


还有关于评论:

  1. 在这种情况下,我知道切片问题 ,并且保证存储在std::array<ProcessedEntryBase>中的派生(模板)类将不会添加更多的数据成员等。 function绑定(铸造)是分开进行的。

  2. 提出的索引技巧 ,也是关于如何在运行时为索引访问解压缩可变参数模板的一个很好的提示,迭代。

为了编译时间的积累发生,你必须有一个编译时间序列。

一个简单的方法是使用variardic模板。 每个条目将是特定元素的标识符和大小,或特定元素的标识符和types。

最高级别的条目将是一个Layout

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

每个条目将是:

 template<typename Key, Key identifier, typename Data> struct Entry {}; 

那么,我们做这样的事情:

 template<typename Key, Key identifier, typename Data, std::size_t Offset> struct ProcessedEntry {}; template<std::size_t offset, typename Key, Key id0, typename D0, typename... Entries> struct LayoutHelper<offset, Key, Entry<Key, id0, D0>, Entries...> { typedef typename prepend < ProcessedEntry< Key, id0, D0, offset > , typename LayoutHelper<offset+sizeof(D0), Key, Entries...>::type >::type type; }; 

使用看起来像:

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

在编写或查找带有元素和tupleprepend ,并在前面加上元素之前,这意味着Layout<blah>::type将包含一个描述数据布局的tuple

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

如果你愿意的话,你可以把这个tuple解包到一个std::array 。 你会使用索引技巧来做到这一点(有很多堆栈溢出的例子,以不同的方式使用这个相同的技巧)。

或者,你可以拿你的ProcessedEntry并添加访问数据的方法,然后编写一个Keysearch编译时程序,遍历tuple ,寻找匹配的Key ,然后返回offsetsize (甚至是types)作为编译时间码。 也许把一个array<N, unsigned char>作为参数,并执行reintepret_cast ,返回一个引用data

通过using别名去除重复的FooEnum将是很好的。