6

I'm trying to implement a map with different access keys using variadic templates in c++. What I want to get is to make such syntax work:

MultikeyMap<int, double, float> map1; // int and double are keys, float is value type 
map1[ 2 ] = 3.5;
map1[ 5.7 ] = 22;

MultikeyMap<unsigned long long, int, float, double, int> map2; // more keys, int is value type
map2[100000000000ULL] = 56;

// etc...

What I have now looks like:

template<class V, class... Krest>
class MultikeyMap;

template<class V, class K, class... Krest>
class MultikeyMap<V, K, Krest...> : protected std::map<K, V>,
                                    protected MultikeyMap<V, Krest...>
{
public:
    template<class T>
    void insert( const T& t, const V& v )
    {
        std::map<T, V>::insert( make_pair( t, v ));
    }

    template<class T>
    const V* find( const T& k )
    {
        typedef std::map<T,V> currentMap;
        currentMap::const_iterator it = currentMap::find( k );
        return it == currentMap::end() ? 0 : &it->second;
    }

};

template<class V>
class MultikeyMap<V>
{};

I didn't return iterators in insert and find to make the code simple.

I see two major defects in this solution.

First, the value type goes first in template arguments list. Initially I tried to write

template<class K, class... Krest, class V>
class MultikeyMap<K, Krest..., V>

but compiler insists that "if an argument for a class template partial specialization is a pack expansion it shall be the last argument".

Second is the protected inheritance from std::maps. I would really like to use composition instead of that, but in that case I don't see a way to access the stored maps. If there was a static_if, I would write

template<class V, class K, class... Krest>
class MultikeyMap<V, K, Krest...> : protected MultikeyMap<V, Krest...>
{
public:
    template<class T>
    void insert( const T& t, const V& v )
    {
        static if( is_same<T,K>::value )
            m_map.insert( make_pair( t, v ));
        else
            MultikeyMap<V, Krest...>::insert( t, v );
    }
private:
    std::map<K,V> m_map;
};

Please advice on the problems I mentioned. If there is a better approach, I'll be glad to learn.

Thanks for reading.

Grigor Gevorgyan
  • 6,753
  • 4
  • 35
  • 64
  • `MultikeyMap`: `int` is cited as key twice, is that really intended ? How are you going to know if the user is looking up by the first or second `int` ? Also, it is unclear to me what you are trying to achieve; should a value be present in all maps at once ? Because if it is just in one map there is no point in having a big common container. – Matthieu M. Oct 03 '13 at 08:13
  • @MatthieuM.: Thanks for the point, there should be one int. The value should be present in one map for now, but later I'm going to add an ability to add another key for already stored value. – Grigor Gevorgyan Oct 03 '13 at 08:33

2 Answers2

2

Easier but not entirely equivalent approaches are probably Boost.Bimap or Boost.MultiIndex.

The former is a map with where keys can lookup values and vice versa, whereas the latter is much more general: it is a container with an arbitrary number of indices, allowing both sequenced ("list-like"), random-access ("vector-like"), associative ("map-like") and hashed access.

You could try to wrap your variadic templates around Boost.MultiIndex, then at least you don't have to reimplement all the insertion/erasure logic (but only thin wrappers).

Note: Boost.MultiIndex does not require a variadic sequence of types, you can also have a variadic sequence of member functions extracting various data members of a user-defined class as the primary data type.

TemplateRex
  • 69,038
  • 19
  • 164
  • 304
2

Here's how I would do it:

template<class V, class K, class... Krest>
class MultikeyMap : MultikeyMap<V, Krest...>,
                    MultikeyMap<V, K>
{
    using ParentMM = MultikeyMap<V, Krest...>;
    using Parent = MultikeyMap<V, K>;
public:
    using ParentMM::insert;
    using Parent::insert;

    using ParentMM::find;
    using Parent::find;

    using ParentMM::operator[];
    using Parent::operator[];
};

template<class V, class K>
class MultikeyMap<V, K>
{
    std::map<K, V> k_map;
public:
    void insert(const K& k, const V& v)
    {
        k_map.insert(std::make_pair(k, v));
    }

    const V* find( const K& k ) const
    {
        auto it = k_map.find(k);
        if (it != k_map.end())
            return &it->second;
        return nullptr;
    }

    V& operator[](const K& k)
    {
        return k_map[k];
    }
};

Inheritance seems appropriate here, as it is combining the behaviour of multiple implementations. I made bases private because the using declaration is required either way to make the members visible. Only the base case has a std::map as a member.

I'm not going to bother reversing the template arguments, it's the same trick used for std::tuple, just look up your favourite STL implementation.

EDIT

Here's the same code, with the trivial change I mentioned, so keys come first in the type parameters:

template<class Head, class... Tail>
struct Helper : Helper<Tail...> {
    using Last = typename Helper<Tail...>::Last;
};

template<class T>
struct Helper<T> {
    using Last = T;
};


template<class K, class... Rest>
class MultikeyMap : MultikeyMap<Rest...>,
                    MultikeyMap<K, typename Helper<Rest...>::Last>
{
    using ParentMM = MultikeyMap<Rest...>;
    using Parent = MultikeyMap<K, typename Helper<Rest...>::Last>;

public:
    using ParentMM::insert;
    using Parent::insert;

    using ParentMM::find;
    using Parent::find;

    using ParentMM::operator[];
    using Parent::operator[];
};

template<class K, class V>
class MultikeyMap<K, V>
{
    std::map<K, V> k_map;
public:
    void insert(const K& k, const V& v)
    {
        k_map.insert(std::make_pair(k, v));
    }

    const V* find( const K& k ) const
    {
        auto it = k_map.find(k);
        if (it != k_map.end())
            return &it->second;
        return nullptr;
    }

    V& operator[](const K& k)
    {
        return k_map[k];
    }
};
DanielKO
  • 4,422
  • 19
  • 29