在 C++ 中创建编译时键值映射
Creating compile-time Key-Value map in C++
我尝试在 C++ 中创建编译时 简单 键值映射。我正在使用 /std:c++11
进行编译。
(嵌入式代码使用IAR编译器,目前只支持cpp++11)
我对元编程有了一些了解。
如果找不到键,我不希望我的地图有默认值,
像这样 post: How to build a compile-time key/value store?
如果在我的代码中我试图获取一个未存储在地图中的值,我想得到编译器错误。
这是我所做的:
#include <iostream>
template <int kk, int vv>
struct KeyValue
{
static const int k = kk, v = vv;
};
// Declaration
template <typename kv, typename...>
struct CompileTimeMap;
// Recursive Definition
template<typename kv, typename... rest>
struct CompileTimeMap<kv, rest...>
{
template<int k_input>
struct get
{
static const int val = (k_input == kv::k) ? kv::v : CompileTimeMap<rest...>::get<k_input>::val;
};
};
// Base Definition
template <typename kv>
struct CompileTimeMap<kv>
{
template<int k_input>
struct get
{
static const int val = (k_input == kv::k) ? kv::v;
};
};
// ----------------------------- Main -----------------------------
typedef CompileTimeMap<KeyValue<10, 20>, KeyValue<11, 21>, KeyValue<23, 7>> mymap;
int main()
{
// This calles should be ok !! :)
std::cout << mymap::get<10>::val << std::endl;
std::cout << mymap::get<11>::val << std::endl;
std::cout << mymap::get<23>::val << std::endl;
// This line should resolve a compile error !! (there is no key of 33)
std::cout << mymap::get<33>::val << std::endl;
}
我收到以下错误:error C2131: expression did not evaluate to a constant
。
我怎样才能完成这项工作?非常感谢:)
不要编写没有必要的模板元程序。试试这个简单的解决方案(CTMap
代表编译时映射):
template <class Key, class Value, int N>
class CTMap {
public:
struct KV {
Key key;
Value value;
};
constexpr Value operator[] (Key key) const
{
return Get (key);
}
private:
constexpr Value Get (Key key, int i = 0) const
{
return i == N ?
KeyNotFound () :
pairs[i].key == key ? pairs[i].value : Get (key, i + 1);
}
static Value KeyNotFound () // not constexpr
{
return {};
}
public:
KV pairs[N];
};
constexpr CTMap<int, int, 3> ctMap {{ { 10, 20 }, { 11, 21 }, { 23, 7 } }};
static_assert (ctMap[10] == 20, "Error.");
static_assert (ctMap[11] == 21, "Error.");
static_assert (ctMap[23] == 7, "Error.");
// constexpr auto compilationError = ctMap[404];
如果取消最后一行 (live demo) 的注释,将会出现编译错误。编译器会将您引导至 KeyNotFound () :
行,从
失败的原因应该很明显。
备注
- 成员变量
pairs
被制作成public,使得用列表初始化来初始化映射成为可能。
- 给定的
N
和初始化 CTMap
的对数应该匹配。如果 N
较小,则会出现编译错误。如果 N
更大,零初始化对 ({ 0, 0 }
) 将被静默添加到 pairs
。注意这个。
- (编译器生成的)构造函数不检查重复键。
operator[]
会找到第一个,但预期用途是您不使用重复键初始化 CTMap
。
- C++14 中不需要递归。我们可以在
constexpr
函数 (live demo) 中编写一个 for
循环。链接的实现给出了另一种想法,即在找不到密钥的情况下给出编译器错误:抛出异常。成员变量 pairs
设为私有。
打算在编译时使用
这是一个线性映射,参数按值传递。我的意图是地图将用于编译时评估代码,这应该不是问题。
另请注意,在 运行 时间内评估时,如果在地图中未找到密钥,此 class 将不会提供任何反馈。
让我们仔细看看 ctMap[10]
在不同情况下的工作原理。我已经用三个编译器(MSVC v19.24、clang 10.0.0、gcc 9.3)尝试了以下内容。
constexpr int C = ctMap[10];
– 即使在调试版本中,全局常量 C
也会被初始化为 20
。在 运行 时间内不进行任何计算。请注意,为确保创建全局,您必须将其地址放在某处。如果您使用 C
的值,它的值 (20
) 将在使用它的地方被替换,并且即使在调试版本中也不会在目标文件中创建 C
。
int Foo () { return ctMap[10]; }
– 在调试版本中 operator[]
将被调用。在 release builds MSVC 内联 operator[]
到 Foo
,即消除了一次调用,但是生成的代码具有线性复杂度(编译器没有强制在编译时进行计算,代码优化在 MSVC 中很差). Clang 和 gcc 编译 return 20;
.
这就是 ctMap[404]
的工作方式(使用相同的三个编译器):
constexpr int C = ctMap[404];
– 不编译,如上所述。
int Foo () { return ctMap[404]; }
– 与 ctMap[10]
相同的注释适用,但 Foo
将 return 0
。您无法知道 404
不在地图中。要得到编译错误,Foo
必须是 constexpr
并强制在编译时通过例如评估。将其分配给 constexpr
变量或枚举器,在模板参数中使用它,作为 C 数组的大小,在 static_assert
中等
我尝试在 C++ 中创建编译时 简单 键值映射。我正在使用 /std:c++11
进行编译。
(嵌入式代码使用IAR编译器,目前只支持cpp++11)
我对元编程有了一些了解。
如果找不到键,我不希望我的地图有默认值, 像这样 post: How to build a compile-time key/value store?
如果在我的代码中我试图获取一个未存储在地图中的值,我想得到编译器错误。
这是我所做的:
#include <iostream>
template <int kk, int vv>
struct KeyValue
{
static const int k = kk, v = vv;
};
// Declaration
template <typename kv, typename...>
struct CompileTimeMap;
// Recursive Definition
template<typename kv, typename... rest>
struct CompileTimeMap<kv, rest...>
{
template<int k_input>
struct get
{
static const int val = (k_input == kv::k) ? kv::v : CompileTimeMap<rest...>::get<k_input>::val;
};
};
// Base Definition
template <typename kv>
struct CompileTimeMap<kv>
{
template<int k_input>
struct get
{
static const int val = (k_input == kv::k) ? kv::v;
};
};
// ----------------------------- Main -----------------------------
typedef CompileTimeMap<KeyValue<10, 20>, KeyValue<11, 21>, KeyValue<23, 7>> mymap;
int main()
{
// This calles should be ok !! :)
std::cout << mymap::get<10>::val << std::endl;
std::cout << mymap::get<11>::val << std::endl;
std::cout << mymap::get<23>::val << std::endl;
// This line should resolve a compile error !! (there is no key of 33)
std::cout << mymap::get<33>::val << std::endl;
}
我收到以下错误:error C2131: expression did not evaluate to a constant
。
我怎样才能完成这项工作?非常感谢:)
不要编写没有必要的模板元程序。试试这个简单的解决方案(CTMap
代表编译时映射):
template <class Key, class Value, int N>
class CTMap {
public:
struct KV {
Key key;
Value value;
};
constexpr Value operator[] (Key key) const
{
return Get (key);
}
private:
constexpr Value Get (Key key, int i = 0) const
{
return i == N ?
KeyNotFound () :
pairs[i].key == key ? pairs[i].value : Get (key, i + 1);
}
static Value KeyNotFound () // not constexpr
{
return {};
}
public:
KV pairs[N];
};
constexpr CTMap<int, int, 3> ctMap {{ { 10, 20 }, { 11, 21 }, { 23, 7 } }};
static_assert (ctMap[10] == 20, "Error.");
static_assert (ctMap[11] == 21, "Error.");
static_assert (ctMap[23] == 7, "Error.");
// constexpr auto compilationError = ctMap[404];
如果取消最后一行 (live demo) 的注释,将会出现编译错误。编译器会将您引导至 KeyNotFound () :
行,从
失败的原因应该很明显。
备注
- 成员变量
pairs
被制作成public,使得用列表初始化来初始化映射成为可能。 - 给定的
N
和初始化CTMap
的对数应该匹配。如果N
较小,则会出现编译错误。如果N
更大,零初始化对 ({ 0, 0 }
) 将被静默添加到pairs
。注意这个。 - (编译器生成的)构造函数不检查重复键。
operator[]
会找到第一个,但预期用途是您不使用重复键初始化CTMap
。 - C++14 中不需要递归。我们可以在
constexpr
函数 (live demo) 中编写一个for
循环。链接的实现给出了另一种想法,即在找不到密钥的情况下给出编译器错误:抛出异常。成员变量pairs
设为私有。
打算在编译时使用
这是一个线性映射,参数按值传递。我的意图是地图将用于编译时评估代码,这应该不是问题。
另请注意,在 运行 时间内评估时,如果在地图中未找到密钥,此 class 将不会提供任何反馈。
让我们仔细看看 ctMap[10]
在不同情况下的工作原理。我已经用三个编译器(MSVC v19.24、clang 10.0.0、gcc 9.3)尝试了以下内容。
constexpr int C = ctMap[10];
– 即使在调试版本中,全局常量C
也会被初始化为20
。在 运行 时间内不进行任何计算。请注意,为确保创建全局,您必须将其地址放在某处。如果您使用C
的值,它的值 (20
) 将在使用它的地方被替换,并且即使在调试版本中也不会在目标文件中创建C
。int Foo () { return ctMap[10]; }
– 在调试版本中operator[]
将被调用。在 release builds MSVC 内联operator[]
到Foo
,即消除了一次调用,但是生成的代码具有线性复杂度(编译器没有强制在编译时进行计算,代码优化在 MSVC 中很差). Clang 和 gcc 编译return 20;
.
这就是 ctMap[404]
的工作方式(使用相同的三个编译器):
constexpr int C = ctMap[404];
– 不编译,如上所述。int Foo () { return ctMap[404]; }
– 与ctMap[10]
相同的注释适用,但Foo
将 return0
。您无法知道404
不在地图中。要得到编译错误,Foo
必须是constexpr
并强制在编译时通过例如评估。将其分配给constexpr
变量或枚举器,在模板参数中使用它,作为 C 数组的大小,在static_assert
中等