Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Create hash queue with variadic template

I want to build a hash code queue using variadic template. The minimal example code is

template<typename T>
void hash_queue(queue<size_t>& q){
  q.push( typeid(T).hash_code() );
}

template<typename T, typename... Ts>
void hash_queue(queue<size_t>& q){
  hash_queue<Ts...>(q);
  q.push( typeid(T).hash_code() );
}

int main(){
  queue<size_t> q;
  hash_queue<int, float, double>(q);
  return 0;
}

On compile I get

main.cpp: In instantiation of ‘void hash_queue(std::queue<long unsigned int>&) [with T = float; Ts = {double}]’:
main.cpp:19:22:   required from ‘void hash_queue(std::queue<long unsigned int>&) [with T = int; Ts = {float, double}]’
main.cpp:25:35:   required from here
main.cpp:19:22: error: call of overloaded ‘hash_queue(std::queue<long unsigned int>&)’ is ambiguous
   hash_queue<Ts...>(q);
                      ^
main.cpp:19:22: note: candidates are:
main.cpp:13:6: note: void hash_queue(std::queue<long unsigned int>&) [with T = double]
 void hash_queue(queue<size_t>& q){
      ^
main.cpp:18:6: note: void hash_queue(std::queue<long unsigned int>&) [with T = double; Ts = {}]
 void hash_queue(queue<size_t>& q){

How could I resolve this? I don't want to create instances of the types. These types will be object classes with constructor classes

like image 565
ztik Avatar asked Mar 06 '15 14:03

ztik


3 Answers

Disambiguate using a second template argument:

template<typename T>
void hash_queue(queue<size_t>& q){
  q.push( typeid(T).hash_code() );
}

template<typename T, typename U, typename... Ts>
void hash_queue(queue<size_t>& q){
  hash_queue<U, Ts...>(q);
  hash_queue<T>(q);
}
like image 198
TartanLlama Avatar answered Oct 17 '22 02:10

TartanLlama


It's also possible to not use recursion at all, and instead pack expand into a std::initializer_list and then push into the queue with a loop.

template<typename... Ts>
void hash_queue(queue<size_t>& q){
  std::initializer_list<size_t> hash_codes = {typeid(Ts).hash_code()...};
  for(auto h : hash_codes)
    q.push( h );
}

Or even shorter:

template<typename... Ts>
void hash_queue(queue<size_t>& q){
  for(auto h : {typeid(Ts).hash_code()...})
    q.push( h );
}

The longer version works even when the pack is empty. The shorter one doesn't because range-for uses auto internally, which can't deduce the type from an empty initializer list.

Note that this pushes into the queue in reverse order compared to your example code. (Give <int, float, double>, it pushes int first and double last; your code pushes double first and int last.) If that's undesired, using the longer form (optionally replacing std::initializer_list<size_t> with auto) and loop manually:

template<typename... Ts>
void hash_queue(queue<size_t>& q){
  std::initializer_list<size_t> hash_codes = {typeid(Ts).hash_code()...};
  for(auto p = hash_codes.end(), end = hash_codes.begin(); p != end; --p)
      q.push( *(p-1) );
}

or in C++14

template<typename... Ts>
void hash_queue(queue<size_t>& q){
  std::initializer_list<size_t> hash_codes = {typeid(Ts).hash_code()...};
  for(auto p = rbegin(hash_codes), end = rend(hash_codes); p != end; ++p)
      q.push( *p );
}
like image 5
T.C. Avatar answered Oct 17 '22 01:10

T.C.


You can use std::enable_if like following :

template<typename T, typename... Ts>
void hash_queue( queue<size_t>& q, 
                 typename std::enable_if<sizeof...(Ts)!=0 >::type* = 0 ){
  hash_queue<Ts...>(q);
  q.push( typeid(T).hash_code() );
}

See demo

like image 1
P0W Avatar answered Oct 17 '22 01:10

P0W