从元组或可变参数模板参数创建数组初始值设置 [英] Creating an array initializer from a tuple or variadic template parameters

查看:226
本文介绍了从元组或可变参数模板参数创建数组初始值设置的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想从一组可变参数中静态嵌入程序代码(最好是ROM部分)中的持久性内存布局(例如Flash或EEPROM设备)的描述,其中自动计算必要的偏移量



目标是创建一个适当的数组初始化器,可以在运行时迭代,而不会对你有限制。 std :: get(std :: tuple) ,这需要编译时索引。






第一种方法



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

 模板
< 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 .size)
,offset(rhs.offset)
{
}
};

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

 模板
< 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 ,NumDataItems> dataItemDescriptors; 

对于客户端我想提供一个数组初始值设定器从 std :: tuple 或可变参数模板参数列表,因此后续数组元素的偏移量将根据前一个元素在编译时的偏移量+大小自动计算。



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

 命名空间
{
static const std :: array< DataItemDescBase< DataItemId :: Values>,4> dataDataLayout =
{{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))
}};
}

但是让客户端手动计算偏移量看起来容易出错和乏味。 p>

TL; DR;是否可以在编译时计算偏移量,如果是,请给我一个草图如何请求?






第二种方法



我已经尝试过 @ Yakk的回答,并为 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_)
, )
,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;

// ...
//通过id查找特定条目的方法
// ...

protected:
LayoutManager (LayoutEntriesArray layoutEntries_)
:layoutEntries(layoutEntries_){
}
};

客户端代码



ConcreteLayout.hpp;

  struct DataItemId {
枚举值{
DataItem1,
DataItem2,
DataItem3,
DataItem4,
};
};

class ConcretePersistentLayout
:public LayoutManager< DataItemId :: Values,4> {
public:
ConcretePersistentLayout();
};






ConcreteLayout.cpp:

 布局< DataItemId :: Values 
, 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; //使用像这样给我一个编译错误
//因为我没有合适的类型'prepend'
//我猜猜
}

ConcretePersistentLayout :: ConcretePersistentLayout()
:LayoutManager< DataItemId :: Values,4>(theDataLayout)
// ^^^^^^这将工作解包元组?
{
}



我希望将访问器类与 LayoutManager ,它接受id,计算持久性内存设备地址,获取数据并强制转换为绑定到键/ id的数据类型。我计划让客户明确指定键/数据类型绑定,因此可以对访问器函数进行静态检查。






最后



根据 @ Yakk的扩展答案









  1. 在这种情况下,我知道切片问题,并且保证存储在 std :: array< ProcessedEntryBase> 中的派生(模板)更多数据成员等。


  2. 索引技巧



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



做这将是使用variardic模板。每个条目将是特定元素的标识符和大小,或特定元素的标识符和类型。



顶级绑定条目将是 Layout

  template< std :: size_t offset,typename Key,typename。条目> 
struct LayoutHelper {
typedef std :: tuple<>类型;
};
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, FooEnum,eFoo,char [10]>,Entry< FooEnum,eFoo2,double> >布局; 

在写入或查找前缀它包含一个元素和一个 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 ...>类型;
};
// use:prepend< int,std :: tuple< double> :: type is std :: tuple< int,double&
//这会删除一些:: type和typename样板文件,如果它在你的编译器中工作:
template< typename T,typename Pack>
using Prepend = typename prepend< T,Pack> :: type;

然后,您会将 tuple a std :: array 。您可以使用索引技巧执行此操作(有许多



或者,你可以把你的 ProcessedEntry 和添加方法以访问数据,然后编写一个搜索编译时程序,该程序遍历 tuple ,寻找匹配 Key ,然后返回 offset size 或者甚至类型)作为编译时代码。可能需要一个数组< N,unsigned char> 作为参数,并执行 reintepret_cast data



删除重复的 FooEnum 通过使用别名。


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:

  1. 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.

  2. The indices trick proposed, also was a good hint about how to unpack variadic template parameters at runtime for indexed access, iterating.

解决方案

In order for compile time accumulation to occur, you have to have a compile time sequence.

An easy way to do this would be to use variardic templates. Each entry would be an identifier and a size of a particular element, or the identifier and type of a particular element.

The top level bundle of entries would be a 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...> {};

Each entry would be:

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

then, we do something like this:

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

use would look like:

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

which, after writing or finding a prepend that takes an element and a tuple, and prepends the element at the front, would mean that Layout<blah>::type would contain a tuple that describes the layout of your data.

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;

You'd then unpack that tuple into a std::array if you wanted. You'd use the indexes trick to do this (there are many examples on stack overflow that use this same trick in different ways).

Or, you could take your ProcessedEntry and add in methods to access data, then write a Key searching compile-time program that walks the tuple, looking for the matching Key, and then returns the offset and size (or even type) as compile time code. Maybe take an array<N, unsigned char> as an argument and do the reintepret_cast, returning a reference-to-data.

Removing the repeated FooEnum would be good via using aliases.

这篇关于从元组或可变参数模板参数创建数组初始值设置的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆