问题
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.
回答1:
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.
回答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];
}
};
来源:https://stackoverflow.com/questions/19153875/multikey-map-using-variadic-templates