在 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 中等