Search code examples
c++11templatesc++14variadic-templatesvariadic

Tuple with Variadic Templates compile error


I am trying to learn variadic templates with a self made tuple implementation. Could someone please explain to me why the following results in a compile error ?

namespace my
{
  // Template definition
  template <typename... Ts> struct tuple;

  template <typename T, typename... Ts>
  struct tuple<T, Ts...> : public tuple<Ts...>
  {
    tuple(T t, Ts... ts) : tuple<Ts...>(ts...), mVal(t) {}
    T mVal;
  };

  template <typename T>
  struct tuple<T>
  {
    tuple(T t) : mVal(t) {}
    T mVal;
  };


  // GetType at an index
  template <size_t i, typename... Ts> struct GetType {};

  template <size_t i, typename T, typename... Ts> 
  struct GetType<i, tuple<T,Ts...> > 
  {
    using Type = typename GetType<i-1, tuple<Ts...> >::Type;
  };

  template <typename T, typename... Ts>
  struct GetType<0, tuple<T,Ts...> >
  {
    using Type = T;
  };


  template <size_t i, typename... Ts>
  typename GetType<i,tuple<Ts...> >::Type Get(tuple<Ts...>& t);

  template <size_t i, typename T, typename... Ts>
  typename GetType<i,tuple<T,Ts...> >::Type Get(tuple<T,Ts...>& t)
  {
    return Get<i-1, tuple<Ts...> >(t);
  }

  template <typename T, typename... Ts>
  typename GetType<0,tuple<T,Ts...> >::Type Get(tuple<T,Ts...>& t)
  {
    return (static_cast<tuple<T, Ts...> >(t)).mVal;
  }  
}

int main()
{
  using myTuple = my::tuple<int, std::string, double, char>;

  // The following lines compile fine ....
  my::GetType<0,myTuple>::Type s0 = 437;
  my::GetType<1,myTuple>::Type s1 = std::string("Test string");
  my::GetType<2,myTuple>::Type s2 = 299.3243;
  my::GetType<3,myTuple>::Type s3 = 'Z';
  std::cout << s0 << " - " << s1 << " - " << s2 << " - " << s3 << std::endl;

  myTuple t(437, "This is the actual tuple string", 299.3243, '§');
  // This line does not compile !!! 
  int v = my::Get<0>(t);

  return 0;
}

My Intention is that the indicated line will use the specialization but looking at the compile error it is obvious that the specialization for value=0 is not being used. Your help is very appreciated.

Thank you for your time ..

c:\users\praka\workspace\cpp\cpp-tests\recipes\tuple\tuple.h(27): error C2039: 'Type': is not a member of 'my::GetType<18446744073709551614,my::tuple<>>'
c:\users\praka\workspace\cpp\cpp-tests\recipes\tuple\tuple.h(27): note: see declaration of 'my::GetType<18446744073709551614,my::tuple<>>'
c:\users\praka\workspace\cpp\cpp-tests\recipes\tuple\tuple.h(43): note: see reference to class template instantiation 'my::GetType<18446744073709551615,my::tuple<my::tuple<std::string,double,char>>>' being compiled
..\test.cpp(15): note: see reference to function template instantiation 'int my::Get<0,int,std::string,double,char>(my::tuple<int,std::string,double,char> &)' being compiled
c:\users\praka\workspace\cpp\cpp-tests\recipes\tuple\tuple.h(27): error C2061: syntax error: identifier 'Type'
c:\users\praka\workspace\cpp\cpp-tests\recipes\tuple\tuple.h(27): error C2238: unexpected token(s) preceding ';'
c:\users\praka\workspace\cpp\cpp-tests\recipes\tuple\tuple.h(43): error C2672: 'Get': no matching overloaded function found
c:\users\praka\workspace\cpp\cpp-tests\recipes\tuple\tuple.h(43): error C2770: invalid explicit template argument(s) for 'GetType<i,my::tuple<Ts...>>::Type my::Get(my::tuple<Ts...> &)'
c:\users\praka\workspace\cpp\cpp-tests\recipes\tuple\tuple.h(38): note: see declaration of 'my::Get'

Solution

  • Off Topic Introductory Suggestion: if you want to play with language to recreate your version of a standard tool (std::tuple, in this case), please, avoid to give the same names to your structs, classes, functions, types...

    This can avoid a lot of strong headaches.

    End of suggestion.

    As observed by Daniel Langr, there isn't partial specialization for template functions.

    So it's difficult to obtain what you want only with functions.

    But structs/classes can partial specialize, so I suggest a getH helper struct written as follows

      template <std::size_t I, typename T, typename ... Ts>
      struct getH
       {
         static typename GetType<I, tuple<T, Ts...>>::Type
            func (tuple<T, Ts...> const & t)
          { return getH<I-1U, Ts...>::func(t); }
       };
    
      template <typename T, typename ... Ts>
      struct getH<0U, T, Ts...>
       {
         static T func (tuple<T, Ts...> const & t)
          { return t.mVal; }
       };
    

    So your Get can be written

      template <std::size_t I, typename ... Ts>
      typename GetType<I, tuple<Ts...>>::Type Get(tuple<Ts...> const & t)
       { return getH<I, Ts...>::func(t); };
    

    If you can use C++14, the return types can be simplified in auto instead typename GetType<I, tuple<T, Ts...>>::Type.

    I observe that in your code you've used static_cast when isn't needed

    You've used it in Get<0>

      template <typename T, typename... Ts>
      typename GetType<0,tuple<T,Ts...> >::Type Get(tuple<T,Ts...>& t)
      {
        return (static_cast<tuple<T, Ts...> >(t)).mVal;
      }  
    

    when mVal is of type T (aka typename GetType<0,tuple<T,Ts...> >::Type)