将编译时定义大小的数组初始化为常量表达式

Initialize array of compile time defined size as constant expression

本文关键字:初始化 数组 常量 表达式 编译 定义      更新时间:2023-10-16

我有一个字符串数组,必须分配一次,并且它们的底层c_str必须在整个程序期间保持有效。

有一些 API 提供有关某些任意数据类型的信息。可能看起来像这样:

// Defined outside my code
#define NUMBER_OF_TYPES 23
const char* getTypeSuffix(int index);

getTypeSuffix不是constexpr,所以这必须在运行时至少部分工作。

必须提供的接口:

// Returned pointer must statically allocated (not on stack, not malloc)
const char* getReadableTypeName(int type);

现在我的数组应该有以下类型:

std::string typeNames[NUMBER_OF_TYPES];

出于我的目的,它将在包装类中初始化,就在构造函数中:

class MyNames
{
MyNames()
{
for (int i = 0; i < NUMBER_OF_TYPES; ++i)
{
names[i] = std::string("Type ") + getTypeSuffix(i);
}
}
const char* operator[](int type) { return _names[(int)type].c_str(); }
private:
std::string _names[NUMBER_OF_TYPES];
};

然后以单例式的方式使用它,例如:

const char* getReadableTypeName(int type) 
{
static MyNames names;
return names[type];
}

现在我想改进的是,我可以看到构造函数中的 for 循环可以这样替换:

MyNames() : _names{std::string("Type ") + getTypeSuffix(0), std::string("Type ") + getTypeSuffix(1), ... , std::string("Type ") + getTypeSuffix(NUMBER_OF_TYPES-1)}
{}

显然是一个伪代码,但你明白了 - 数组可以直接初始化,让构造函数没有主体,这很整洁。这也意味着数组成员_names可以const,进一步强制正确使用这个辅助类。

我很确定在编译时通过表达式填充数组还有许多其他用途,而不是循环。我什至怀疑这是在03期间无论如何都会发生的事情.

有没有办法编写具有灵活长度并由表达式定义的 C++11 样式数组初始值设定项列表?另一个简单的例子是:

constexpr int numberCount = 10;
std::string numbers[] = {std::to_string(1), std::to_string(2), ... , std::to_string(numberCount)};

同样,使用表达式而不是循环。

我问这个问题不是因为我试图大幅提高性能,而是因为我想了解 C++14 及更高版本新的、整洁的功能。

而不是C数组使用std::array,那么你可以编写你的函数来返回该std::array然后你的成员可以被const

std::array<std::string, NUMBER_OF_TYPES> build_names()
{
std::array<std::string, NUMBER_OF_TYPES> names;
for (int i = 0; i < NUMBER_OF_TYPES; ++i)
{
names[i] = std::string("Type ") + getTypeSuffix(i);
}
return names;
}

class MyNames
{
MyNames() : _names(build_names()) {}
const char* operator[](int type) const { return _names[(int)type].c_str(); }
private:
const std::array<std::string, NUMBER_OF_TYPES> _names;
};

现在你有std::array,你可以使用可变参数模板而不是循环,比如(std::index_sequence的东西是C++14,但可以在C++11中实现):

template <std::size_t ... Is> 
std::array<std::string, sizeof...(Is)> build_names(std::index_sequence<Is...>)
{
return {{ std::string("Type ") + getTypeSuffix(i) }};
}

然后调用它:

MyNames() : _names(build_names(std::make_index_sequence<NUMBER_OF_TYPES>())) {}

您可以遵从初始化函数:

std::array<std::string, NUMBER_OF_TYPES> initializeNames()
{
std::array<std::string, NUMBER_OF_TYPES> names;
for (int i = 0; i < NUMBER_OF_TYPES; ++i) {
names[i] = std::string("Type ") + getTypeSuffix(i);
}
return names;
}
const char* getReadableTypeName(int type) 
{
static auto const names = initializeNames();
return names[type].c_str();
}

它可以是立即调用的 lambda:

static auto const names = []{
std::array<std::string, NUMBER_OF_TYPES> names;
// ...
return names;
}();

还是您真的需要array要求?无论如何,我们正在制作字符串,所以我不明白,那么您可以使用 range-v3:

char const* getReadableTypeName(int type) {
static auto const names =
view::iota(0, NUMBER_OF_TYPES)
| view::transform([](int i){ return "Type "s + getTypeSuffix(i); })
| ranges::to<std::vector>();
return names[type].c_str():
}

您可以在 C++14 中使用std::make_integer_sequence和委托构造函数(std::make_integer_sequence的实现存在于 C++11 中,因此这不是真正的C++特定于 14)来获取整数的模板参数包

#include <string>
#include <utility>
#define NUMBER_OF_TYPES 23
const char* getTypeSuffix(int index);
class MyNames
{
MyNames() : MyNames(std::make_integer_sequence<int, NUMBER_OF_TYPES>{}) {}
template<int... Indices>
MyNames(std::integer_sequence<int, Indices...>) : _names{ (std::string("Type ") + getTypeSuffix(Indices))... } {}
const char* operator[](int type) { return _names[(int)type].c_str(); }
private:
const std::string _names[NUMBER_OF_TYPES];
};

这意味着默认构造的字符串没有。

既然你渴望使用新功能,让我们使用range-v3(即将成为 C++2a 中ranges库)来编写一些非常短的代码:

const char* getReadableTypeName(int type) 
{
static const std::vector<std::string> names =
view::ints(0, 23) | view::transform([](int i) {
return "Type " + std::to_string(i);
});
return names[type].c_str();
}

https://godbolt.org/z/UVoENh