6
\$\begingroup\$

If you're familiar with std::tuple, you're aware that accessing its elements is slightly unusual. Instead of using a member function, we're provided with the function std::get instead. I was curious as to why this is, so I conducted a brief search. The few websites that I saw all gave the same reason: the code used to called the method is ugly.

We would expect:

my_tuple.get<0>()

but apparently we would have to write:

my_tuple.template get<0>()

(a source, see footnote 5).

This seemed like a bad case of "I heard that she heard that he heard...", because I was unable to find an implementation of a tuple that exposed this ugliness by including get as a member. I heard that Boost's tuple had get as a member, but after inspecting the source I discovered that it also only accepted 10 elements max. It was time for me to implement it myself!

template <typename... Ts>
class Tuple
{

};

template <std::size_t Index, typename... Rs>
struct TypeHolder;

template <std::size_t Index, typename R, typename... Rs>
struct TypeHolder<Index, Tuple<R, Rs...>>
{
    typedef typename TypeHolder<Index - 1, Tuple<Rs...>>::type type;
};

template <typename R, typename... Rs>
struct TypeHolder<0, Tuple<R, Rs...>>
{
    typedef R type;
};

template <typename T, typename... Ts>
class Tuple<T, Ts...> : public Tuple<Ts...>
{
public:
    Tuple(T obj, Ts... objs)
        : Tuple<Ts...>{ std::forward<Ts>(objs)... }
        , obj{obj}
    {

    }

    template <std::size_t Index>
    typename std::enable_if<
        Index == 0,
        typename TypeHolder<Index, Tuple<T, Ts...>>::type&
    >::type get()
    {
        return obj;
    }

    template <std::size_t Index>
    typename std::enable_if<
        Index != 0,
        typename TypeHolder<Index, Tuple<T, Ts...>>::type&
    >::type get()
    {
        return static_cast<Tuple<Ts...>*>(this)->get<Index - 1>();
    }

protected:
    T obj;
};

This functions as expected, and after some brief testing I found that there were no issues with storing pointers or references. But there was no need to write "template" for every call to get! (My compiler is GCC, by the way.)

Is there a reason why std::tuple was not implemented in this way? I would like to know about any glaring flaws with this code before I move forward with it.

\$\endgroup\$
4
\$\begingroup\$

Your source is not very specific as to why/when my_tuple.template get<0>() is required.

If you have code like this:

Tuple<int, double, float> x(1,2,3);
int i = x.get<0>();

That is fine, because all template arguments to Tuple are known at the time of parsing and Tuple<int, double, float> is instantiated immediately, before the call to get is parsed. At that point the compiler already knows that in that specific instantiation of Tuple get is member function template rather than a non-template member and everything is ok.

This however

template<typename T>
auto foo(T t) {
    Tuple<T,int,double> x(t,2,3);
    return x.get<0>();
}

generates a compiler error, because when foo is parsed, the actual type of T is not known and instantiation of Tuple<T,int,double> needs to be delayed until instantiation of foo. This means that when x.get is parsed, the compiler does not yet know what kind of language construct get is in the instatiation of Tuple<T,int,double> and in fact it may very well vary for different T. Therefore it is assumed that get is a non-template member, which doesn't make sense considering the following template argument list.

Your code sadly does not solve this problem and I don't think it is solvable.

Additionally I noticed that your Tuple is an empty class if an empty type list is given. That is a bit inconsistent. I think at least get should be provided as empty function. Calling it should not be allowed, but referring to the member function should be fine.

\$\endgroup\$
3
  • \$\begingroup\$ I see! In your second block of code, changing the return line to return x.template get<0>() results in successful compilation, but that is exactly what everyone was seeking to avoid. \$\endgroup\$
    – user118534
    Sep 26 '16 at 19:17
  • \$\begingroup\$ @user118534 Yes exactly. I think it is categorically impossible to avoid that. It is required by the standard for all template members on dependent types. \$\endgroup\$
    – user116966
    Sep 26 '16 at 19:18
  • \$\begingroup\$ That could be a resaonable explanation as to why there's no member get in the standard instead of the helper function - but doesn't explain why you can't have both. The problem is that a free function requires sometimes struggles with namespaces and symbol conflicts, and this problem with the member happens only in very rare cases - in which you can simply use the free function, if member's usage is so ugly. \$\endgroup\$
    – Ethouris
    May 23 '19 at 17:19

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.