【问题标题】:Creating compile-time Key-Value map in C++在 C++ 中创建编译时键值映射
【发布时间】:2020-07-31 13:23:27
【问题描述】:

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

我对元编程有所了解。

如果找不到键,我不希望我的地图有默认值, 喜欢这篇文章: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

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

【问题讨论】:

  • consts 必须是 constexpr
  • 这不起作用的原因有很多:1)在constexpr中禁止内存分配,因为当没有分配器运行时您无法分配内存。这意味着没有字符串、向量或任何容器映射。将 int 映射到 int 可能没问题,但随后您将面临另一个问题:2) C++11 的 constexpr 非常有限 并且只允许非常简单的表达式而没有任何局部变量。您至少需要 C++17 才能获得更好的元编程体验

标签: c++ templates metaprogramming key-value template-meta-programming


【解决方案1】:

不要在没有必要的地方编写模板元程序。试试这个简单的解决方案(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 是公开的,以便可以使用列表初始化来初始化地图。
  • 给定的N 和初始化CTMap 的对数应该匹配。如果N 更小,则会出现编译错误。如果N 更大,零初始化对({ 0, 0 })将被静默添加到pairs。请注意这一点。
  • (编译器生成的)构造函数不检查重复键。 operator[] 会找到第一个,但预期用途是不要使用重复键初始化 CTMap
  • 在 C++14 中不需要递归。我们可以在 constexpr 函数 (live demo) 中编写 for 循环。链接的实现提供了另一种想法,即在找不到密钥的情况下给出编译器错误:抛出异常。成员变量pairs 设为私有。

打算在编译时使用

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

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

让我们仔细看看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]; – 如上所述,无法编译。
  • int Foo () { return ctMap[404]; } – 与ctMap[10] 相同的注释适用,但Foo 将返回0。你不知道,404 不在地图中。要得到编译错误,Foo 必须是 constexpr 并强制在编译时被评估,例如将其分配给 constexpr 变量或枚举器,在模板参数中使用它,作为 C 数组的大小,在 static_assert 中使用,等等。

【讨论】:

  • 这个“地图”在 O(n) 中查找一个值,这根本不是真正的地图
  • 不幸的是,我正在使用 IAR 编译器(嵌入式代码),目前仅支持 c++11 :(
  • 你能帮我把你的实现改成递归实现吗(支持 c++ 11 吗?)——我们能避免抛出异常吗(不能在嵌入式中使用)
  • @phuclv 在运行时是 O(1),不是吗?
  • 我编辑了答案以解决上述主题。解释了 O(n)O(1) 复杂性,解决方案现在无一例外地在 C++11 中,尽管我保留了 C++ 的链接14 版本。
猜你喜欢
  • 2014-11-22
  • 1970-01-01
  • 2017-05-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多