C++ - How to call a templated method of a recursively inherited templated base class

Viewed 101

I'm trying to create a Tuple class with an arbitrary number of entries with arbitrary types using variadic templates and the ability to get the nth entry with a templatized entry method, so I can use it as follows:

Tuple<int, int, std::string, double> t(1, 2, "Hello World", 3.4);
std::cout << t.entry<1>() << std::endl; // Prints 2
std::cout << t.entry<2>() << std::endl; // Prints "Hello World"

My current approach:

template<typename ...Types>
struct Tuple;

template<typename Type>
struct Tuple<Type>
{
    Tuple(Type value) : value(value) { };
    Type value;

    template<int Index>
    Type& entry()
    {
        return value;
    }
};

template<typename Type, typename... Types>
struct Tuple<Type, Types...> : public Tuple<Types...>
{
    Tuple(Type value, Types ...args) : Tuple<Types...>(args...), value(value) { }
    Type value;

    template<int Index>
    auto entry() -> decltype(Tuple<Types...>::entry<Index-1>())&
    {
        return Tuple<Types...>::entry<Index-1>();
    }

    template<>
    Type& entry<0>() 
    {
        return value;
    }
};

The first struct providing the "base" case for one element and the second struct building recursively upon that. However, I get the error

In member function ‘decltype (((Tuple<Types ...>::entry < (Index - 1)) > <expression error>))& Tuple<Type, Types ...>::entry()’:
error: expected primary-expression before ‘)’ token

How can I call a templated method of a templated base class?

2 Answers

You need add a template before ::entry

template<int Index> // ...................VVVVVVVVV
auto entry() -> decltype(Tuple<Types...>::template entry<Index-1>())&
{ // .......................VVVVVVVVV
    return Tuple<Types...>::template entry<Index-1>();
}

or the < after ::entry is parsed as a relational operator.

But you have another problem: the specialization of entry():

template<>
Type& entry<0>() 
{
    return value;
}

Unfortunately you can't specialize the a method without specializing the containing class.

If you can compile C++17, you can avoid method specialization and use if constexpr

template <int Index>
auto & entry()
 {
   if constexpr ( Index == 0 )
      return value;
   else 
      return Tuple<Types...>::template entry<Index-1>();
 }

Pre C++17 In C++14... I suppose you can solve using tag dispatching

template <int>
Type & entry_helper (std::true_type)
 { return value; }

template <int Index>
auto & entry_helper (std::false_type)
 { return Tuple<Types...>::template entry<Index-1>(); }

template <int Index>
auto & entry()
{ return entry_helper<Index>(std::integral_constant<bool, Index==0>{}); }

In C++11 you need also the trailing return type in for entry() and for the second entry_helper()

As pointed by Patrick Roberts (thanks!) the solution, adding trailing return type, works for C++11 with g++ but not for clang++, for a problem in detecting the return type in a context of recursion.

For C++11 I propose a completely different solution that avoid the entry()/entry_helper() recursion but add another level of indirection at class level (add a recursive base class struct Tpl). Add also perfect forwarding, unsigned indexes and const-versions for entry() and entry_helper().

#include <utility>
#include <iostream>
#include <type_traits>

template <std::size_t, typename...>
struct Tpl
 { void entry_helper () {} };

template <std::size_t I, typename T, typename ... Ts>
struct Tpl<I, T, Ts...> : public Tpl<I+1u, Ts...>
 {
   using Tpl<I+1, Ts...>::entry_helper;

   Tpl (T && t, Ts && ... ts)
      : Tpl<I+1u, Ts...>{std::forward<Ts>(ts)...}, value{std::forward<T>(t)}
    { }

   T value;

   T & entry_helper (std::integral_constant<std::size_t, I>)
    { return value; }

   T const & entry_helper (std::integral_constant<std::size_t, I>) const
    { return value; }
 };

template <typename ... Ts>
struct Tuple : public Tpl<0, Ts...>
 {
   using Tpl<0, Ts...>::entry_helper;

   Tuple (Ts && ... ts) : Tpl<0u, Ts...>{std::forward<Ts>(ts)...}
    { }

   template <std::size_t I>
   auto entry ()
    -> decltype(entry_helper(std::integral_constant<std::size_t, I>{})) &
    { return entry_helper(std::integral_constant<std::size_t, I>{}); }

   template <std::size_t I>
   auto entry () const
    -> decltype(entry_helper(std::integral_constant<std::size_t, I>{})) const &
    { return entry_helper(std::integral_constant<std::size_t, I>{}); }
 };

int main()
 {
   Tuple<int, int, std::string, double> t(1, 2, "Hello World", 3.4);

   std::cout << t.entry<1>() << std::endl; // Prints 2
   std::cout << t.entry<2>() << std::endl; // Prints "Hello World"
 }

Assuming you're using at least C++14, you can use auto& instead of a trailing return type, and as pointed out by max66 you need to add the template keyword before the member function invocation in your syntax.

You can also simplify your definition because you just need an empty base class, not a class which implements a specialization for one type; your second class already implements the necessary behavior for one type. This simplification requires you to rewrite entry() using std::enable_if, or if constexpr if you're using C++17

// only needed for C++14 when using std::enable_if
#include <type_traits>

template<typename...>
struct Tuple
{
};

template<typename T, typename... Ts>
struct Tuple<T, Ts...> : public Tuple<Ts...>
{
    Tuple(T value, Ts ...args) : value(value), Tuple<Ts...>(args...) { }
    T value;

    template<std::size_t I, std::enable_if_t<I == 0, bool> = true>
    T& entry() { return value; }

    template<std::size_t I, std::enable_if_t<I != 0, bool> = true>
    auto& entry() { return Tuple<Ts...>::template entry<I - 1>(); }

    // // requires C++17 support
    // template<std::size_t I>
    // auto& entry() {
    //     if constexpr (I == 0) { return value; }
    //     else { return Tuple<Ts...>::template entry<I - 1>(); }
    // }
};

Try it on godbolt.org

Related