如何在C++中的编译时创建成员变量的集合



目前我有这个:

#include <iostream>
#include <array>
struct ItemBase {
virtual void print() const = 0;
};
template <typename T> struct Item : ItemBase {
T m_value {};
void print() const override { 
std::cout << m_value << std::endl;
}
};
struct Items {
constexpr Items() {}//constexpr helps in ensuring constant initialization
Item<int>    m_itemA;
Item<double> m_itemB;
// Many more items will be added and deleted often

//ItemBase * m_items[2] {
//Or
std::array<ItemBase *, 2> m_items {
&m_itemA,
&m_itemB,
};
void print() {
for(ItemBase * item : m_items) {
item->print();
}
}
};
Items items; //global scope. Should be placed directly to .data without any runtime constructor
int main(int argc, char ** argv) {
items.m_itemA.m_value = 123; //Some sample use
items.m_itemB.m_value = 1.23;
items.print();
return 0;
}

每次需要添加/删除m_itemX时,总是修改m_items数组大小和初始值设定项,这是非常乏味且容易出错的。我如何欺骗C++编译器为我做这件事?

一个明显的解决方案是使用宏,比如:

#define ITEMS(WRAPPER) 
WRAPPER(Item<int>, m_itemA) 
WRAPPER(Item<double>, m_itemB)
#define WRAPPER_INSTANCE(type, name) type name;
#define WRAPPER_POINTER(type, name) &name,
#define WRAPPER_0(type, name) 0,
struct Items {
constexpr Items() {}//constexpr helps in ensuring constant initialization
ITEMS(WRAPPER_INSTANCE);

std::array<ItemBase *, std::size( { ITEMS(WRAPPER_0) } ) > m_items {
ITEMS(WRAPPER_POINTER)
};
...

这是可行的,但宏太难看了,IDE代码浏览很混乱。

有更好的解决方案吗?

可以向m_itemX实例化添加一些参数或包装器。但不允许运行时构造函数,当然也不允许分配——否则我可以使用向量或列表。

(根据实现、对齐和填充,可以将m_size变量添加到ItemBase,并让print((简单地扫描Items的主体,而不是保留m_Items数组(

这应该可以通过使用可变模板和std::tuple来实现。我将为如何声明它绘制一个粗略的蓝图,它应该清楚地表明整个实现的样子。

#include <iostream>
#include <array>
#include <utility>
#include <tuple>
struct ItemBase {
virtual void print() const = 0;
};
template <typename T> struct Item : ItemBase {
T m_value {};
void print() const override {
std::cout << m_value << std::endl;
}
};
template<typename tuple_type, typename index_sequence> struct ItemsBase;
template<typename ...Type, size_t ...n>
struct ItemsBase<std::tuple<Type...>,
std::integer_sequence<size_t, n...>> {
constexpr ItemsBase() {}
std::tuple<Item<Type>...> m_itemsImpl;
std::array<ItemBase *, sizeof...(Type)> m_items{
&std::get<n>(m_itemsImpl)...
};
void print()
{
// ... see below
}
};
template<typename ...Types>
using ItemsImpl=ItemsBase<std::tuple<Types...>,
std::make_index_sequence<sizeof...(Types)>>;
typedef ItemsImpl<int, double> Items;

您现有的Items类只是ItemsImpl<int, doule>的别名。每当您想添加另一个字段时,只需添加一个模板参数即可。

项目的所有实例都被收集到一个std::tuple中,并且m_items被相应地初始化。

实现各种方法取决于您的C++版本。对于C++17,它只是一个简单的折叠表达式:

void print() {
( std::get<n>(m_itemsImpl).print(), ...);
}

对于C++11或C++14,这仍然是可行的,但(可能(需要使用一些辅助类来完成更多的工作。

我发现了一种方法:在编译时在constexpr容器中组装项指针。用法如下:

struct Items {
...
static inline Item<int>    m_itemA;
container_add(&m_itemA);
static inline Item<double> m_itemB;
container_add(&m_itemB);
...
};

我使用了和源__LINE__相关联的模板专门化的想法,它引用了以前的模板专业化,来自C++是否支持编译时计数器?

首先,我们声明ItemContainer的递归专用化,它从前一行复制items容器:

template<unsigned int Line> struct ItemContainer 
{ static constexpr inline auto items { ItemContainer<Line-1>::items }; };

然后专门用于空容器的行0:

template<> struct ItemContainer<0> { static constexpr inline std::tuple<> items {}; };

然后对给定的__LINE__进行显式专门化,并添加一个新项目:

#define container_add(newItem) template<> struct ItemContainer<__LINE__> {
static inline constexpr auto items { 
std::tuple_cat ( ItemContainer<__LINE__-1>::items, std::tuple<ItemBase*> (newItem) ) }; }

然后是累积容器的访问者:

#define container_last ItemContainer<__LINE__>

打印:

void print() {
std::apply([](auto&&... args) { ( ( 
args? (void) args->print() : (void) (std::cout << (void*)args << std::endl)
), ...);}, container_last::items );
}

一个限制是它需要C++17。

另一个主要限制是它只能用于静态项——全局变量或类的静态成员。这对我的目的来说是可以的,因为我的项目无论如何都是全局变量。

此外,由于以下错误,目前此代码不使用g++编译:bug 85282-CWG727(非命名空间范围内的完全专业化(这是一个暂时的问题,希望他们最终能解决。目前,它可以使用VS 2017中的clang++10.0.0和cl 19.16.27024.1进行编译。此错误仅在添加类的成员时发生。g++仍然允许我们在类范围之外添加全局变量。

此外,我希望消除任何项目名称重复,但在这里,我仍然必须在相邻行中键入每个项目名称两次,但这比必须在完全不同的地方输入名称要好得多,因为这很容易出错。

最后的挑战是选择合适的物品容器。我尝试了std::tuple、std::array、自定义列表和递归类型名引用。我在上面展示了tuple版本——它是最短的,但编译速度最慢,并且支持的项数最少。自定义列表是最快的,并且允许最大的项目计数(使用cl时为1000甚至10000(。所有这些版本都会生成非常高效的代码-容器本身根本不存在于RAM中,print((函数被编译为对单个项的一系列调用->print((,其中项地址是常量。

以下是使用自定义列表的完整实现:

struct ListItem {
ItemBase       * m_item;
const ListItem * m_prev;
int              m_count;
};
template <int N>
constexpr std::array<ItemBase*,N> reverse_list(const ListItem * last) {
std::array<ItemBase*,N> result {};
for(int pos = N-1; pos >= 0 && last && last->m_item; pos--, last = last->m_prev) {
result[pos] = last->m_item;
}
return result;
}
struct Items {
constexpr Items() {}//constexpr helps in ensuring constant initialization
/*
Idea of template specialization, which references previous template specialization is from:
https://stackoverflow.com/a/6210155/894324
There is gcc bug "CWG 727 (full specialization in non-namespace scope)": 
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=85282
Explicit specialization in class scope should have been allowed in C++17, 
but g++ 9.3.0 in 2021 still says "error: explicit specialization in non-namespace scope"
So, until g++ is fixed we should use clang++ or MS cl
*/
template<unsigned int Line> struct ItemContainer 
{ static constexpr inline ListItem m_list { ItemContainer<Line-1>::m_list }; };
template<> struct ItemContainer<0> { static constexpr inline ListItem m_list {nullptr, nullptr, 0}; };
#define container_last ItemContainer<__LINE__>
#define container_add(newItem) template<> struct ItemContainer<__LINE__> {  
static constexpr inline ListItem m_list { 
newItem, 
&ItemContainer<__LINE__-1>::m_list, 
ItemContainer<__LINE__-1>::m_list.m_count + 1 
};     }
static inline Item<int>    m_itemA;
container_add(&m_itemA);

//.... Thousands of extra items can be added ....

static inline Item<long long>    m_itemB;
container_add(&m_itemB);

void print() {
std::cout << "list (last to first):" << std::endl;
for(const ListItem * item = &container_last::m_list; item && item->m_item; item = item->m_prev) {
item->m_item->print();
}
std::cout << "reversed:" << std::endl;
const auto reversed_list = reverse_list<container_last::m_list.m_count>(&container_last::m_list);
std::apply([](auto&&... args) { ( ( 
args? (void) args->print() : (void) (std::cout << (void*)args << std::endl)
), ...);}, reversed_list );
}
};

最新更新