3

Today, while using the same template for the hundredth time, I came across the idea to introduce a simple replacement.

Instead of

 QHash<int,QHash<int,QHash<int,int> > >

it should now be:

 MultiKeyHash<int,int,int,int>

(Not to be confused with QT's QMultiHash class).

Whereas the first three arguments are the types of the keys and the last argument is the type of the value.

 MultiKeyHash<TKey1,TKey2,....,TKeyN,TValue>

My current implementation looks like this:

template <typename... Args>
struct MultiKeyHash;

template <typename TKey1, typename... Args>
struct MultiKeyHash<TKey1, Args...> : QHash<TKey1,MultiKeyHash<Args...> > {

    typedef typename MultiKeyHash<Args...>::ValueType ValueType;
    const ValueType& value(const TKey1 &key1, const Args_Without_Last&... args) const {
          return operator[](key1).value(args...);
    }
};

template <typename TKeyN, typename TValue>
struct MultiKeyHash<TKeyN,TValue> : QHash<TKeyN,TValue> {
    typedef TValue ValueType;
};

Everything worked as expected until I wanted to add the "value" method. I certainly can't use "Args" as Type for the second parameter(-pack). Do I have to make a function template of it, or is there a way around?

How do I have to define the "value" method in order to be able to call it like:

value(key1,key2,...,keyn)
Constructor
  • 7,273
  • 2
  • 24
  • 66
Timo
  • 1,724
  • 14
  • 36
  • If you can change your design/requirement to make the value type the first parameter of the template, you won't need to figure out `Args_Without_Last`. – R Sahu Aug 12 '14 at 20:34
  • Shouldn't `value` be returning by reference? Returning copies of multilevel hash tables seems expensive. I assume you omitted the `&` on the return type, and that it was not your intent to use the "return a constant value" anti-pattern. – Casey Aug 12 '14 at 20:38
  • @KerrekSB The last element of `Args` is the value type, not one of the key types. – Casey Aug 12 '14 at 20:40
  • @Casey: Ah, of course. – Kerrek SB Aug 12 '14 at 20:43
  • @RSahu Nice thought. Maybe I could take the value type as first parameter, and reverse the parameter order with another template... – Timo Aug 12 '14 at 20:54
  • @Casey I simply copied the definition from Qt's QHash. But you are right, it's much nicer that way. thx. – Timo Aug 12 '14 at 20:56

2 Answers2

3

Here's working version of MultiHashKey. It uses a dummy implementation of QHash for testing purposes.

#include <iostream>

// Dummy implementation of QHash for testing purposes.
template <typename TKey, typename TValue>
struct QHash
{
   void insert(const TKey& key, const TValue& val) {this->val = val;}
   const TValue& value(const TKey& key) const { return val; }
   TValue val;
};

template <typename... Args> struct MultiKeyHash;

template <typename TKey, typename... Args>
struct MultiKeyHash<TKey, Args...> : QHash<TKey, MultiKeyHash<Args...>>
{
   typedef TKey KeyType;
   typedef typename MultiKeyHash<Args...>::ValueType ValueType;
   typedef QHash<KeyType, MultiKeyHash<Args...>> QHashType;

   using QHashType::insert;
   using QHashType::value;

   MultiKeyHash() {}

   MultiKeyHash(const TKey &key, const Args&... args)
   {
      this->insert(key, MultiKeyHash<Args...>(args...));
   }

   void insert(const TKey &key, const Args&... args)
   {
      MultiKeyHash<Args...> val(args...);
      this->insert(key, val);
   }
   template <typename TKey1, typename ... Args1>
   const ValueType& value(const TKey1 &key1, const Args1&... args1) const
   {
      MultiKeyHash<Args...> const& val = this->value(key1);
      return val.value(args1...);
   }
};

template <typename TKey, typename TValue>
struct MultiKeyHash<TKey, TValue> : QHash<TKey, TValue> 
{
   typedef TKey KeyType;
   typedef TValue ValueType;
   typedef QHash<KeyType, ValueType> QHashType;

   MultiKeyHash() {}

   MultiKeyHash(const TKey &key, const TValue& val)
   {
      this->insert(key, val);
   }
};

void test1()
{
   MultiKeyHash<int, double> hash;
   hash.insert(10, 200.5);
   double v = hash.value(10);
   std::cout << "Value: " << v << std::endl;
}

void test3()
{
   MultiKeyHash<int, int, int, double> hash;
   hash.insert(10, 10, 10, 20.5);
   double v = hash.value(10, 10, 10);
   std::cout << "Value: " << v << std::endl;
}

int main()
{
   test1();
   test3();
   return 0;
};

Output of running the program:

Value: 200.5
Value: 20.5
R Sahu
  • 204,454
  • 14
  • 159
  • 270
  • Accepted Answer, for providing a complete solution with an additional insert() method. – Timo Aug 13 '14 at 10:48
2

If you make value a variadic template, you avoid the problem altogether at the cost of nastier error messages since conversions will happen inside the function instead of at the callsite:

template <typename First, typename...Remainder>
const ValueType& value(First&& first, Remainder&&... rest) const {
    return value(std::forward<First>(first)).value(std::forward<Remainder>(rest)...);
}
Casey
  • 41,449
  • 7
  • 95
  • 125