在 C++ 中创建编译时键值映射

Roi*_*iT7 9 c++ templates metaprogramming key-value template-meta-programming

我尝试在 C++ 中创建一个编译时简单的键值映射。我正在编译/std:c++11. (使用IAR编译器进行嵌入代码,目前仅支持cpp++11)

我了解了一些关于元编程的知识。

如果找不到键,我不希望我的映射具有默认值,例如这篇文章:如何构建编译时键/值存储?

如果在我的代码中我试图获取未存储在映射中的值,我想得到编译器错误。

这是我所做的:


#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;
}
Run Code Online (Sandbox Code Playgroud)

我收到以下错误:error C2131: expression did not evaluate to a constant

我怎样才能做到这一点?非常感谢 :)

Dr.*_*Gut 9

如果没有必要,不要编写模板元程序。尝试这个简单的解决方案(CTMap代表编译时映射):

\n
template <class Key, class Value, int N>\nclass CTMap {\npublic:\n    struct KV {\n        Key   key;\n        Value value;\n    };\n\n    constexpr Value  operator[](Key key) const\n    {\n        return Get(key);\n    }\n\nprivate:\n    constexpr Value  Get(Key key, int i = 0) const\n    {\n        return i == N ?\n               KeyNotFound() :\n               pairs[i].key == key ? pairs[i].value : Get(key, i + 1);\n    }\n\n    static Value KeyNotFound()      // not constexpr\n    {\n        return {};\n    }\n\npublic:\n    KV  pairs[N];\n};\n\n\nconstexpr CTMap<int, int, 3>  ctMap{{ { 10, 20 }, { 11, 21 }, { 23, 7 } }};\n\n\nstatic_assert(ctMap[10] == 20, "Error.");\nstatic_assert(ctMap[11] == 21, "Error.");\nstatic_assert(ctMap[23] ==  7, "Error.");\n\n// constexpr auto compilationError = ctMap[404];\n
Run Code Online (Sandbox Code Playgroud)\n

如果取消最后一行的注释(现场演示) ,您将收到编译错误。编译器将引导您到该KeyNotFound() :行,从该行失败的原因应该是显而易见的。

\n

评论

\n
    \n
  • 成员变量pairs被公开,以便可以使用聚合初始化来初始化映射。
  • \n
  • 给定的N和初始化的对的数量CTMap应该匹配。如果N小于,则会出现编译错误。如果N更大,则零初始化对 ( { 0, 0 }) 将默默地添加到pairs。请注意这一点。
  • \n
  • (编译器生成的)构造函数不会检查重复的键。operator[]会找到第一个,但预期用途是不使用CTMap重复的键进行初始化。
  • \n
  • C++14 中不需要递归。for我们可以在函数中编写循环constexpr现场演示)。链接的实现提供了另一种想法,即在找不到密钥的情况下给出编译器错误:抛出异常。成员变量pairs设为私有。
  • \n
\n

旨在在编译时使用

\n

这是一个线性映射,参数按值传递。我的意图是该映射将在编译时评估的代码中使用,这应该不是问题。

\n

另请注意,在运行时评估时,如果在映射中未找到该键,此类将不会提供任何反馈。

\n

让我们仔细看看ctMap[10]不同情况下的工作原理。我已经使用三个编译器(MSVC v19.24、clang 10.0.0、gcc 9.3)尝试了以下操作。

\n
    \n
  • constexpr int C = ctMap[10];\xe2\x80\x93即使在调试版本中C也会初始化全局常量。20运行时不进行任何计算。请注意,为了确保创建全局变量,您必须将其地址放在某处。如果您使用 的值C,则其值 ( 20) 将在使用它的地方被替换,并且C即使在调试版本中也不会在目标文件中创建。
  • \n
  • int Foo() { return ctMap[10]; }\xe2\x80\x93 在调试版本中将operator[]被调用。在发布版本中,MSVC 内联operator[]Foo,即消除了一次调用,但生成的代码具有线性复杂度(编译器不会被迫在编译时进行计算,并且 MSVC 中的代码优化很差)。Clang 和 gcc 编译一个return 20;.
  • \n
\n

这就是ctMap[404]工作原理(使用相同的三个编译器):

\n
    \n
  • constexpr int C = ctMap[404];\xe2\x80\x93 如上所述,无法编译。
  • \n
  • int Foo() { return ctMap[404]; }\xe2\x80\x93 与 相同的注释适用ctMap[10],但Foo将返回0。你不可能知道,那404不在地图上。要获得编译错误,Foo必须constexpr并强制在编译时进行计算,例如将其分配给constexpr变量或枚举器,在模板参数中使用它,作为 C 数组的大小,在static_assert等中。
  • \n
\n

  • 我编辑了答案以解决上述主题。_O(n)_ 或 _O(1)_ 复杂度已得到解释,解决方案现在无一例外地采用 C++11,尽管我保留了 C++14 版本的链接。 (2认同)