问题描述
目标是将大型数据结构序列化为 constexpr
初始化,因此它们将成为 .text
或 .rodata
段的一部分并可用于 consteval
、{ {1}} 等
数据结构包含用于交叉引用/索引等的容器。所有这些代码都会生成,因此我们可以使用相同或等效的容器逻辑在代码生成器内部计算容器状态,然后按原样序列化容器状态。
C++ 不支持 constexpr
的非瞬态动态分配。
“在 Kona 2019 中,这种方法被认为过于脆弱,因此从功能集中删除了非瞬态分配。” http://open-std.org/JTC1/SC22/WG21/docs/papers/2019/p0784r7.html
因此,无法使用 constexpr
或自制的动态容器。
(可变参数?)模板的使用似乎也是不可能的,因为数据结构必须以运行时动态方式可用,即模板的任何使用都需要在顶部有一个类型擦除层,这看起来很笨拙。见https://www.artima.com/articles/on-the-tension-between-object-oriented-and-generic-programming-in-c
正如这里所探讨的,下一个尝试是尝试将序列化为复合文字。见https://en.cppreference.com/w/c/language/compound_literal
标准 C++20 实际上并不正式支持复合文字,但我们可以依靠编译器扩展来支持 C++ 中的这个 C 特性。似乎在所有主要的 C++ 编译器中都可用。
下面的例子只是一个非常小的概念证明(或尝试)。
问题:以下内容适用于 Clang,但不适用于 GCC。 为什么?有什么不同的方法吗?
std::
在 GCC 上这失败了。
#include <memory>
template <typename K,typename V>
struct Entry{
K key;
V value;
};
template <typename K,typename V>
struct Bucket{
size_t n;
// C++ cannot initialize a flexible array in a struct,use a pointer.
Entry<K,V> *entries;
};
template <typename K,typename V>
struct HashMap{
using bucket_t = Bucket<K,V>;
using entry_t = Entry<K,V>;
size_t n;
// C++ cannot initialize a flexible array in a struct,use a pointer.
bucket_t *buckets;
constexpr V get(K key) const {
const size_t i = key % n;
const auto &bucket = buckets[i];
for (size_t j = 0; j < bucket.n; ++j) {
if (bucket.entries[j].key == key) {
return bucket.entries[j].value;
}
}
return {};
}
};
using MyHashMap = HashMap<int,double>;
constexpr MyHashMap map =
{
2,// Because we can't initialize a flexible array,we need to employ a so-called
// compound literal and let it decay to a pointer.
(MyHashMap::bucket_t[])
{
{
3,(MyHashMap::entry_t[]) // dito
{ {0,1.2},{2,2.4},{6,3.6} }
},{
4,(MyHashMap::entry_t[]) // dito
{ {1,0.0},{5,2.0},{11,4.0},{13,1.0} }
}
}
};
// Serves as proof of concept by succeeding as consteval.
consteval int proofOfConcept() {
// Should return 76 as a constant,return 10*(map.get(0) + map.get(11) + map.get(2));
}
int main() {
return proofOfConcept();
}
在这里探索:
https://godbolt.org/z/nMKnoY6fY
解决方法
正如康桓卫所指出的,这是一个终生的问题。似乎每个嵌套数组文字都需要单独定义为一个符号,然后在其位置使用。这也避免了非标准的复合字面用法。
但是它增加了传递常量的问题。我们需要能够使用常量参数化包含结构。
所以这似乎是一个解决方案:
#include <memory>
template<bool isConst,typename T>
struct TransitiveConst { using Result = T; };
template<typename T>
struct TransitiveConst<true,T> { using Result = const T; };
template <typename K,typename V>
struct Entry{
K key;
V value;
};
template <typename E>
struct Bucket{
size_t n;
// C++ cannot initialize a flexible array in a struct,use a pointer.
E *entries;
};
template <bool isConst,typename K,typename V>
struct HashMap{
using entry_t = typename TransitiveConst<isConst,Entry<K,V>>::Result;
using bucket_t = typename TransitiveConst<isConst,Bucket<entry_t>>::Result;
size_t n;
// C++ cannot initialize a flexible array in a struct,use a pointer.
bucket_t *buckets;
constexpr V get(K key) const {
const size_t i = key % n;
const auto &bucket = buckets[i];
for (size_t j = 0; j < bucket.n; ++j) {
if (bucket.entries[j].key == key) {
return bucket.entries[j].value;
}
}
return {};
}
};
using MyHashMap = HashMap<true,const int,const double>;
constexpr MyHashMap::entry_t entry0[] = { {0,1.2},{2,2.4},{6,3.6} };
constexpr MyHashMap::entry_t entry1[] = { {1,0.0},{5,2.0},{11,4.0},{13,1.0} };
constexpr MyHashMap::bucket_t buckets[] = {
{
std::size(entry0),entry0
},{
std::size(entry1),entry1
}
};
constexpr MyHashMap map =
{
std::size(buckets),buckets
};
// Serves as proof of concept by succeeding as consteval.
consteval int proofOfConcept() {
// Should return 76 as a constant,return (10*(map.get(0) + map.get(11) + map.get(2)));
}
int main() {
return proofOfConcept();
}