从元组或可变参数模板参数创建数组初始值设定项

时间:2013-08-15 11:32:16

标签: c++ c++11 template-meta-programming

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

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


第一种方法

我创建了一个简单的数据项描述符类,它将特定的ID(应该由客户端作为枚举类型提供)绑定到数据布局(偏移量和大小):

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

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

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或可变参数模板参数列表中提供数组初始值设定项,因此后续数组元素的偏移量将在编译时自动计算前一个元素的偏移量+大小时间。

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

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's answer的提案,并为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)) {
    }
};

我本来打算使用一个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,计算持久性内存设备地址,获取数据并强制转换为绑定到key / id的数据类型。我计划让客户端明确指定键/数据类型绑定,因此可以对访问器函数进行静态检查。


最后

在第一轮要求进一步澄清之后,我现在正在制作一些基于@Yakk's extended answer的内容。


同样关于评论:

  1. 在这种情况下,我知道slicing problem,并且保证std::array<ProcessedEntryBase>中存储的派生(模板)类不会再添加任何数据成员等。功能性绑定(铸造)是单独完成的。

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

1 个答案:

答案 0 :(得分:3)

为了进行编译时累积,必须有一个编译时序列。

一种简单的方法是使用可变参数模板。每个条目都是标识符和特定元素的大小,或特定元素的标识符和类型。

顶级条目包将是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;

在写完或找到带有元素和prepend的{​​{1}}并在前面添加元素之后,意味着tuple将包含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。您可以使用indexes trick来执行此操作(堆栈溢出有很多示例以不同的方式使用相同的技巧)。

或者,您可以使用std::array并添加方法来访问数据,然后编写ProcessedEntry搜索编译时程序,遍历Key,查找匹配的{{ 1}},然后返回tupleKey(或甚至类型)作为编译时代码。也许以offset作为参数并执行size,返回引用 - array<N, unsigned char>

通过reintepret_cast别名删除重复的data会很好。