首页 > 解决方案 > 在 C++ 中创建编译时键值映射

问题描述

我试图在 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;
}

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

我怎样才能使这项工作?非常感谢 :)

标签: c++templatesmetaprogrammingkey-valuetemplate-meta-programming

解决方案


不要在没有必要的地方编写模板元程序。试试这个简单的解决方案(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];

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

评论

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

打算在编译时使用

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

另请注意,在运行时评估时,如果在地图中找不到键,则此类不会提供任何反馈。

让我们仔细看看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[]中将被调用。在发行版中将 MSVC 内联operator[]Foo,即消除了一次调用,但生成的代码具有线性复杂度(编译器不会强制在编译时进行计算,并且 MSVC 中的代码优化很差)。Clang 和 gcc 编译一个return 20;.

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

  • constexpr int C = ctMap[404]; – Does not compile, as mentioned above.
  • int Foo () { return ctMap[404]; } – The same remarks apply as for ctMap[10], but Foo will return 0. You cannot know, that 404 was not in the map. To get the compilation error, Foo has to be constexpr and forced to be evaluated in compile time by e.g. assigning it to a constexpr variable or an enumerator, using it in a template argument, as a size of a C array, in a static_assert, etc.

推荐阅读