c++templatesmetaprogrammingkey-valuetemplate-meta-programming

Creating compile-time Key-Value map in C++


I have tried to create a compile-time simple Key-Value map in C++. I'm compiling with /std:c++11. (Using IAR compiler for embedded code and only cpp++11 is supported at the moment)

I've learnt a little bit about meta-programming.

I don't want my map to have a default value, if key is not found, like this post: How to build a compile-time key/value store?

I want to get compiler error, if in my code I'm trying to get a value which is not stored in the map.

Here is what I've done:


#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;
}

I get the following error: error C2131: expression did not evaluate to a constant.

How can I make this work? Many thanks :)


Solution

  • Don't write a template metaprogram, where it is not necessary. Try this simple solution (CTMap stands for compile time map):

    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];
    

    You will get a compilation error, if you uncomment the last line (live demo). The compiler will direct you to the KeyNotFound() : line, from which the reason of the failure should be obvious.

    Remarks

    Intended to be used in compile time

    This is a linear map, and parameters are passed by value. My intention was that the map will be used in compile time evaluated code, where this should not be a problem.

    Note also that when evaluated in run time, this class won't give any feedback if the key is not found in the map.

    Let's take a closer look of how ctMap[10] works in different situations. I have tried the following with three compilers (MSVC v19.24, clang 10.0.0, gcc 9.3).

    And this is how ctMap[404] works (with the same three compilers):