Confusion while deriving from std::tuple, can not handle std::get
Asked Answered
P

1

12

My basic idea was to derive my own class from std::tuple to get some helper types inside like this:

template <typename ... T>
class TypeContainer: public std::tuple<T...>
{   
    public:
        using BaseType = std::tuple<T...>;
        static const size_t Size = sizeof...(T);
        TypeContainer(T... args):std::tuple<T...>(args...){};

        using index_sequence = std::index_sequence_for<T...>;
};

Now I try to use the code as follows:

using MyType_tuple_with_empty =         std::tuple<       std::tuple<float,int>,    std::tuple<>,    std::tuple<int>>;
using MyType_typecontainer_with_empty = TypeContainer< TypeContainer<float,int>, TypeContainer<>, TypeContainer<int>>;

using MyType_tuple_non_empty =          std::tuple<       std::tuple<float,int>,    std::tuple<int>,    std::tuple<int>>;
using MyType_typecontainer_non_empty =  TypeContainer< TypeContainer<float,int>, TypeContainer<int>, TypeContainer<int>>;

template <typename T>
void Do( const T& parms )
{
    // The following lines result in errors if TypeContainer with
    // empty element comes in. The empty element is in std::get<1> which
    // is NOT accessed here!
    std::cout << std::get<0>(std::get<0>(parms)) << " ";
    std::cout << std::get<1>(std::get<0>(parms)) << std::endl;

    std::cout << std::get<0>(std::get<2>(parms)) << std::endl;
}


int main()
{
    MyType_tuple_with_empty         p1{{ 1.2,3},{},{1}};
    Do( p1 );

    MyType_typecontainer_with_empty p2{{ 1.2,3},{},{1}};
    Do( p2 ); // << this line raise the error

    MyType_tuple_non_empty          p3{{ 1.2,3},{4},{1}};
    Do( p3 );

    MyType_typecontainer_non_empty  p4{{ 1.2,3},{4},{1}};
    Do( p4 );
}

If I compile with Do(p2) I get the following error:

error: no matching function for call to 'get(const TypeContainer<TypeContainer<float, int>, TypeContainer<>, TypeContainer<int> >&)'

Can someone explain why the existence of an empty TypeContainer in connection with std::get will result in that problem?

Edit: Additional information:

The lines

MyType_tuple_with_empty         p1{{{ 1.2,3},{},{1}}};
MyType_tuple_non_empty          p3{{ 1.2,3},{4},{1}};

can not compiled with gcc5.2.0 but with gcc6.1.0. This is a bit mysterious because I remember that the constructor of the tuple is indeed explicit. Why this works with gcc6.1.0? But that is not the problem I search for :-)

Another hint: The code which I have problems with seems to compile with clang3.5.0.

A bit hard to understand...

Edit2: Digging through the error lists ( a long one :-) ) I found:

/opt/linux-gnu_5.2.0/include/c++/5.2.0/tuple|832 col 5| note: template argument deduction/substitution failed: main.cpp|104 col 45| note: 'std::tuple<_Elements ...>' is an ambiguous base class of 'TypeContainer<TypeContainer<float, int>, TypeContainer<>, TypeContainer<int> >' || std::cout << std::get<0>(std::get<0>(parms)) << " ";

Seems that in libg++ someone derives multiple times from any tuple type which seems to be a broken library. Searching for this topic brings me to: Empty nested tuples error

Is this really related? Same bug or a new one :-)

Palmira answered 12/5, 2016 at 12:8 Comment(2)
perhaps a simplified casePoinsettia
This is a gcc bug that was fixed in GCC 11. (The core issue being std::is_base_of_v<std::tuple<>, std::tuple<std::tuple<>>> through private inheritance in the buggy implementation). Related Q: https://mcmap.net/q/684560/-is-an-implementation-of-std-tuple-allowed-to-fail-with-triggering-a-derived-to-base-conversion-for-empty-class-elements, showing your code should work and GCC was buggyFreeness
A
4

Unfortunately you have to add your container versions of get functions:

template <std::size_t I, typename ...T>
decltype(auto) get(TypeContainer<T...>&& v)
{
    return std::get<I>(static_cast<std::tuple<T...>&&>(v));
}
template <std::size_t I, typename ...T>
decltype(auto) get(TypeContainer<T...>& v)
{
    return std::get<I>(static_cast<std::tuple<T...>&>(v));
}
template <std::size_t I, typename ...T>
decltype(auto) get(TypeContainer<T...> const& v)
{
    return std::get<I>(static_cast<std::tuple<T...> const&>(v));
}

And just use get not std::get in Do kind of functions. Compiler is able to select namespace from arguments.

I guess, I am not sure, that this is because gcc has EBO - Empty Base Optimization - implemented in its tuples. What are exact reason it is quite hard to guess. You might consider to report this in gcc bugzilla.


BTW, it is not good habit to derive from STD classes. If you started from compisition, not inheritance, then you would need to provide your own get functions and you would not observe this error, saving probably a lot of time.

Avulsion answered 12/5, 2016 at 13:12 Comment(3)
Thanks for your reply. There are 2 things I think about: First: accessing a tuple with an empty tuple should not behave different to access a tuple with non empty tuples. Second: clang works. I will try your code as workarround for the problem, but I believe there is a bug in libg++... another one. I have already list of bugs reported... with no response :-(Palmira
I am almost 99% sure this is because EBO - I looked into implementation - there are some template magic things being done to have EBO - that is not easy to read, neither I have time to do it. Please report this. And consider to use composition. CLang probably does not have EBO, or implemented it in better way.Avulsion
And why EBO - because gcc implements tuple by inheritance: each tuple element add new level of inheritance - when some element is empty - this very base class would occupy some memory - so it is something that could be optimizedAvulsion

© 2022 - 2024 — McMap. All rights reserved.