【问题标题】:How to build a compile-time key/value store?如何构建编译时键/值存储?
【发布时间】:2013-05-05 15:27:46
【问题描述】:

我有一个问题,我需要在编译时将一个整数映射到另一个整数。基本上,我需要std::map<int,int> 的编译时等价物。如果在地图中找不到键,我想返回一个默认值。

我想使用的界面:

template<unsigned int default_value,
         unsigned int key0, unsigned int value0,
         unsigned int key1, unsigned int value1,
         ...>
struct static_map
{
  ...
};

template<unsigned int key, typename StaticMap>
struct lookup
{
  static unsigned int value = ...
};

lookup 返回与StaticMap 中的key 关联的值。如果找不到key,则返回default_value

一般来说,键/值对的数量会受到一定的限制 > 2。构建 static_maplookup 的最佳方法是什么?

我还应该提到,我仅限于使用 C++03 语言结构,因此没有 C++11,也没有外部库依赖项。


这是我得到的解决方案,灵感来自 n.m.和 DyP 的回答如下:

#include <iostream>

template<unsigned int k, unsigned int v>
struct key_value
{
  static const unsigned int key = k;
  static const unsigned int value = v;
};


template<typename Head, typename Tail = void>
struct cons
{
  template<unsigned int key, unsigned int default_value>
  struct get
  {
    static const unsigned int value = (key == Head::key) ? (Head::value) : Tail::template get<key,default_value>::value;
  };
};


template<typename Head>
struct cons<Head,void>
{
  template<unsigned int key, unsigned int default_value>
  struct get
  {
    static const unsigned int value = (key == Head::key) ? (Head::value) : default_value;
  };
};


template<unsigned int default_value,
         unsigned int key0, unsigned int value0,
         unsigned int key1, unsigned int value1,
         unsigned int key2, unsigned int value2,
         unsigned int key3, unsigned int value3,
         unsigned int key4, unsigned int value4,
         unsigned int key5, unsigned int value5,
         unsigned int key6, unsigned int value6,
         unsigned int key7, unsigned int value7>
struct static_map
{
  template<unsigned int key>
  struct get
  {
    typedef cons<
      key_value<key0,value0>,
      cons<
        key_value<key1,value1>,
        cons<
          key_value<key2,value2>,
          cons<
            key_value<key3,value3>,
            cons<
              key_value<key4,value4>,
              cons<
                key_value<key5,value5>,
                cons<
                  key_value<key6,value6>,
                  cons<
                    key_value<key7,value7>
                  >
                >
              >
            >
          >
        >
      >
    > impl;

    static const unsigned int value = impl::template get<key,default_value>::value;
  };
};


template<unsigned int key, typename StaticMap>
struct lookup
{
  static const unsigned int value = StaticMap::template get<key>::value;
};


int main()
{
  typedef static_map<13, 
                     0, 0,
                     1, 10,
                     2, 20,
                     3, 30,
                     4, 40,
                     5, 50,
                     6, 60,
                     7, 70
  > my_static_map;

  std::cout << "0 maps to " << lookup<0, my_static_map>::value << std::endl;
  std::cout << "1 maps to " << lookup<1, my_static_map>::value << std::endl;
  std::cout << "2 maps to " << lookup<2, my_static_map>::value << std::endl;
  std::cout << "3 maps to " << lookup<3, my_static_map>::value << std::endl;
  std::cout << "4 maps to " << lookup<4, my_static_map>::value << std::endl;
  std::cout << "5 maps to " << lookup<5, my_static_map>::value << std::endl;
  std::cout << "6 maps to " << lookup<6, my_static_map>::value << std::endl;
  std::cout << "7 maps to " << lookup<7, my_static_map>::value << std::endl;
  std::cout << "100 maps to " << lookup<100, my_static_map>::value << std::endl;

  return 0;
}

【问题讨论】:

  • @user93353:此问题与您链接的问题无关。
  • @DyP:是的,我需要类似 boost 的 MPL 地图。不幸的是,我不能依赖 MPL,所以我想了解它的实现。
  • @JaredHoberock 哎呀,MPL 映射适用于类型。可能不是你要找的..
  • 限制是固定的还是需要多张不同大小的地图?

标签: c++ template-meta-programming


【解决方案1】:

在 C++11 中:

template <int kk, int vv>
struct kv
{
    static const int k = kk, v = vv;
};

template <int dflt, typename...>
struct ct_map;

template <int dflt>
struct ct_map<dflt>
{
    template<int>
    struct get
    {
        static const int val = dflt;
    };
};

template<int dflt, int k, int v, typename... rest>
struct ct_map<dflt, kv<k, v>, rest...>
{
    template<int kk>
    struct get
    {
        static const int val =
            (kk == k) ?
            v :
            ct_map<dflt, rest...>::template get<kk>::val;
    };
};

typedef ct_map<42, kv<10, 20>, kv<11, 21>, kv<23, 7>> mymap;

#include <iostream>
int main()
{
    std::cout << mymap::get<10>::val << std::endl;
    std::cout << mymap::get<11>::val << std::endl;
    std::cout << mymap::get<23>::val << std::endl;
    std::cout << mymap::get<33>::val << std::endl;
}

【讨论】:

  • 不需要定义kv::kkv::v(在课堂外)吗?
  • @DyP:不,因为它们没有被 ODR 使用。
  • 如果找不到键,你将如何抛出编译时错误?
  • @karliwson 在默认实例化中粘贴静态断​​言,并去掉第一个参数
【解决方案2】:

这样的事情会起作用:

template<int Key>
struct StaticMap {
  static const int Value = 0;
};

template<>
struct StaticMap<1> {
  static const int Value = 3;
};

int main()
{
  cout << StaticMap<0>::Value << ", " 
       << StaticMap<1>::Value << ", "
       << StaticMap<2>::Value << endl;
}

0 是默认值,键为 1 的值为 3。根据需要添加其他特化。

这是您要查找的内容的大致思路吗?这不是您要求的界面,尽管预处理器宏(例如 Boost.Preprocessor)可以简化和简化设置。

【讨论】:

    【解决方案3】:

    您可以使用模板特化

    template <char key>
    struct Map;
    
    template <char key>
    struct Map { static const int value = -1; }; // not exists node
    
    template <> struct Map< 'A' > { static const int value = 1; }; // 'A' -> 1
    template <> struct Map< 'B' > { static const int value = 2; }; // 'B' -> 2
    // ....
    
    int lookup = Map<'B'>::value; // = 2
    

    您可以利用一些宏来简化内容的定义。

    【讨论】:

      【解决方案4】:

      基本上基于继承:每个映射实例都继承其基类的查找类型(-> 归约问题)并定义键的查找。

      编辑:根据 n.m. 的想法改进版本。

      #include <iostream>
      #include <cstddef>
      
      template < int t_key, int t_value >
      struct ct_int_pair
      {
          enum { key = t_key, value = t_value };
      };
      
      struct dummy;
      
      template < int default_value,
                 typename key_value_pair0,
                 typename key_value_pair1 = dummy,
                 typename key_value_pair2 = dummy >
      struct ct_map
          : ct_map < default_value, key_value_pair1, key_value_pair2, dummy >
      {
          typedef ct_map < default_value, key_value_pair1, key_value_pair2, dummy > base;
      
          // DUMMY required for partial specialization
          template < int key, class DUMMY = dummy >
          struct lookup
          {
              enum { value = base::template lookup < key > :: value };
          };
            template < class DUMMY >
            struct lookup < key_value_pair0::key, DUMMY >
            {
                enum { value = key_value_pair0::value };
            };
      };
      
        template < int default_value >
        struct ct_map < default_value, dummy, dummy, dummy >
        {
            template < int key >
            struct lookup
            {
                enum { value = default_value };
            };
        };
      
      
      template < int key, typename StaticMap >
      struct lookup
      {
          enum { value = StaticMap :: template lookup < key > :: value };
      };
      
      
      // example
      
      typedef ct_map < -1,
                       ct_int_pair<21, 42>,
                       ct_int_pair<10, 15> > my_map;
      
      enum
      {
           value0 = lookup<21, my_map>::value
         , value1 = lookup<10, my_map>::value
         , value2 = lookup<100, my_map>::value
      };
      
      int main()
      {
          std::cout << value0 << " : " << value1 << " : " << value2 << std::endl;
      }
      

      【讨论】:

        【解决方案5】:

        Similar question(尽管 2 年后问),我的回答类似,但由于这里没有 17 标准的版本,这是我的:

        #include <type_traits>
        #include <tuple>
        
        //tag for typenames
        template <class T>
        struct tag_type
        {
            using type = T;
        };
        
        //tag for autos
        template <auto val>
        struct tag_auto
        {
            constexpr static decltype(val) value = val;
        };
        
        //generic pair
        template <typename key_tag, typename val_tag>
        struct cexpr_pair
        {
            using key = key_tag;
            using value = val_tag;
        };
        
        template <class ... cexpr_pairs>
        class cexpr_generic_map
        {
            template <typename cexpr_tag_key, size_t iter = 0>
            constexpr static auto Find()
            {
                //failed to find by key
                if constexpr (iter == sizeof...(cexpr_pairs))
                    //you can substitute void with "tag_auto<default_value>"
                    return cexpr_pair<cexpr_tag_key, void>();
                else
                {
                    typedef std::tuple_element_t<iter, std::tuple<cexpr_pairs...>> cur_pair;
                    if constexpr (std::is_same_v<cexpr_tag_key, cur_pair::key>)
                        return cur_pair();
                    else 
                        return Find<cexpr_tag_key, iter + 1>();
                }
            }
        
        public:
        
            template <typename tag_key>
            using found_pair = decltype(Find<tag_key>());
        };
        

        使用示例:

        typedef cexpr_generic_map<
        cexpr_pair<tag_auto<0>, tag_auto<4>>,
        cexpr_pair<tag_auto<1>, tag_auto<8>>,
        cexpr_pair<tag_auto<2>, tag_auto<15>>,
        cexpr_pair<tag_auto<4>, tag_auto<0>>,
        cexpr_pair<tag_auto<8>, tag_auto<1>>,
        cexpr_pair<tag_auto<15>, tag_auto<2>>
        > map_inverse;
        
        
        
        static_assert(map_inverse::found_pair<tag_auto<0>>::value::value == 4);
        static_assert(map_inverse::found_pair<tag_auto<1>>::value::value == 8);
        static_assert(map_inverse::found_pair<tag_auto<2>>::value::value == 15);
        
        static_assert(map_inverse::found_pair<tag_auto<4>>::value::value == 0);
        static_assert(map_inverse::found_pair<tag_auto<8>>::value::value == 1);
        static_assert(map_inverse::found_pair<tag_auto<15>>::value::value == 2);
        

        还有一个奖励: 由于此映射是“通用的”,因此键和值都可以是任何类型或值的标记。此外,它们可以是任何类型。并且各种 cexpr_pairs 可以一起存储。你只需要以某种方式强制它拥有唯一的键。

        使用示例:

        struct abc
        {
            void func1(int i, double d)
            {
                std::cout << i << '\n' << d << '\n';
            }
        
            int func2()
            {
                return 9;
            }
        };
        
        typedef cexpr_pair<tag_auto<1>, tag_auto<&abc::func1>> pair_func1;
        typedef cexpr_pair<tag_auto<2>, tag_auto<&abc::func2>> pair_func2;
        typedef cexpr_pair<tag_type<int>, tag_auto<&abc::func2>> int_func2;
        typedef cexpr_pair<abc, tag_auto<18>> custom1;
        typedef cexpr_pair<int, abc> custom2;
        
        typedef cexpr_generic_map<pair_func1, pair_func2, int_func2, custom1, custom2> map_funcs;
        
            int main()
        {
            abc a;
            (a.*map_funcs::found_pair<tag_auto<1>>::value::value)(6, 3.28);                         // 6 3.28
        
            std::cout << (a.*map_funcs::found_pair<tag_auto<2>>::value::value)() << std::endl;      // 9
            std::cout << (a.*map_funcs::found_pair<tag_type<int>>::value::value)() << std::endl;    // 9 
            std::cout << map_funcs::found_pair<abc>::value::value << std::endl;                     // 18
            map_funcs::found_pair<int>::value().func1(4, 8.15162342);                               // 4 8.15162342
        
            std::cin.get();
            return 0;
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2014-01-24
          • 1970-01-01
          • 2014-05-28
          • 2020-02-20
          • 2020-07-31
          相关资源
          最近更新 更多