当前位置: 首页 > 知识库问答 >
问题:

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

管翼
2023-03-14

我想表示从一组可变模板参数静态嵌入程序代码(最好是在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;博士可以在编译时计算偏移量吗?如果可以,你能给我一个草图吗?

我尝试了@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_)
    , 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_) {
    }
};

客户代码

具体布局。水电站;

struct DataItemId {
    enum Values {
        DataItem1 ,
        DataItem2 ,
        DataItem3 ,
        DataItem4 ,
    };
};

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

具体ayout.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的数据类型。我计划让客户端显式指定键/数据类型绑定,这样就可以对访问器函数进行静态检查。

根据@Yakk在第一轮要求进一步澄清后的扩展回答,我现在有一些东西正在制作中。

关于评论:

>

  • 在这种情况下,我知道切片问题,并且可以保证存储std::array中的派生(模板)类

    提出的索引技巧也很好地提示了如何在运行时解压可变模板参数以进行索引访问、迭代。


  • 共有1个答案

    强硕
    2023-03-14

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

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

    顶级条目束将是一个布局

    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;
    

    其中,在编写或找到一个前缀后,它接受一个元素和一个元组,并在前面前缀该元素,这意味着Layout

    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;
    

    然后,如果需要,您可以将该元组解压缩为std::array。您可以使用索引技巧来实现这一点(有许多堆栈溢出的例子以不同的方式使用相同的技巧)。

    或者,您可以使用ProcessedEntry并添加访问数据的方法,然后编写一个搜索编译时程序,该程序遍历元组,查找匹配的,然后返回偏移量大小(甚至是类型)作为编译时代码。可能需要一个数组

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

     类似资料:
    • 本质上,我想要一个具有数组的模板类,其大小是一个模板参数,以保存常量内容。 类似于: 我一直在搜索和修补一点,几乎有一个解决方法实现了一个中间静态方法,并使用std::array: ...这已经是相当多的样板,但仍然d::array似乎不是从初始化列表中构建的?:-(

    • 问题内容: 这行代码给出以下警告: 警告:变量sh只能在此位置为null。 并且,此代码给出以下警告: 警告:局部变量sh可能尚未初始化。 问题答案: 这是因为您需要初始化数组。试试这个: 如果不初始化,则会收到这些警告,如果运行它也会得到警告。

    • 问题内容: 我正在努力查看将值传递给函数时使用哪种方法是否有明显的优势。下面的代码可能不是解释我要做出的决定的最佳示例,但我认为这是最容易理解的示例。 可变参数方法 数组参数法 两种技术中的哪一种是首选?如果是这样,为什么(速度,可靠性或只是易于阅读)?谢谢。 问题答案: 我认为没有速度差异。因为,在功能内部,您可以像一样使用。 我认为如果参数数量较少(例如小于5个),则因为易于阅读,可能是一个更

    • 问题内容: 我得到错误: TestCounter.java:115:变量计数器可能尚未初始化counters [i] = new Counter(i); 而且我不知道如何解决它。我知道我的课程“” 有效。下面是我的代码,如果您可以看一下,我将非常高兴。此代码包装在类的main方法中。 问题答案: 您尚未创建数组,只是声明了变量。 您需要这样做: 或类似的东西

    • 我有两个数组:和。 数组中的示例值:。 数组中的值示例:。 我需要创建一个JavaScript对象,将数组中的所有项放在同一个对象中。例如

    • 问题内容: 假设我有一个枚举,然后实例化一个天数组。 如何将一天(例如)设置为所有“天”的默认值?如果如上所述进行设置,则所有元素均为空。我希望通过枚举表现得更像ints和Strings,它们分别初始化为0和“”。 问题答案: 正如其他人所说,枚举是引用类型-它们只是特定类的编译器语法糖。JVM不了解它们。这意味着该类型的默认值为null。当然,这不仅会影响数组- 这意味着类型为枚举的任何字段的初