5

from time to time it happens that I want to change a type I defines via using by a real class.

For example I here a sample, how I can use a struct the same way as a type:

using t_int_pair = std::pair< int, int >;
struct s_int_pair : public std::pair< int, int >
{
    using std::pair< int, int >::pair; // inherit ctor
};

void foo()
{
    auto [a1, a2] = t_int_pair{ 0, 0 };
    auto [b1, b2] = s_int_pair{ 0, 0 };
    // ...
}

This works fine.

But: for any reason the same code doesn't work for std::tuple, i.e. this produces a compile error:

using t_int_triple = std::tuple< int, int, int >;
struct s_int_triple : public std::tuple< int, int, int >
{
    using std::tuple< int, int, int >::tuple; // inherit ctor
};

void bar()
{
    auto [a1, a2, a3] = t_int_triple{ 0, 0, 0 };
    auto [b1, b2, b3] = s_int_triple{ 0, 0, 0 }; // ERROR: cannot decompose class type 'std::_Tuple_impl<0, int, int>'
    // ...
}

Does anyone know, why this happens?

And is there a way to workaround this?

I tested this on Compiler Explorer with clang, gcc and msvc.

Any help is appreciated,

regards,

Zoppo

Zoppo
  • 53
  • 4
  • 1
    No, the initialization works well, what fails is the structured binding. – user202729 Jan 30 '21 at 14:00
  • There are https://stackoverflow.com/questions/45699005/providing-tuple-like-structured-binding-access-for-a-class and https://stackoverflow.com/questions/7967746/tuple-size-and-an-inhereted-class-from-tuple concerning that. – user202729 Jan 30 '21 at 14:03
  • I am surprised inheriting from std pair allowes structured decomposition to work. Maybe the public members? That might be why. – Yakk - Adam Nevraumont Jan 30 '21 at 15:23

2 Answers2

1

Pair is an aggregate with public members, tuple does not.

Your inherit-from-pair is using the aggregate structured bindings.

Tuple uses the tuple machinery. Some of the tuple machinery doesn't work with inheritance for good reason.

So you need to specialize std::tuple_size<your_type> to expose the tuple size.

struct s_int_triple : public std::tuple< int, int, int >
{
  using std::tuple< int, int, int >::tuple; // inherit ctor
};
namespace std{
  template<>
  class tuple_size<::s_int_tuple>:public std::integral_constant<std::size_t, 3>{};
  template< std::size_t I >
  class tuple_element<I,::s_int_tuple>:public tuple_element<I, std::tuple<int,int,int>>{};
}

and structured binding should work.

Yes this sucks.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • (`std::get` automatically works because the class has `std::tuple` as a base case, right?) – user202729 Jan 30 '21 at 17:11
  • @user2 yes, overload resolution considers bases, while template specialization does not. – Yakk - Adam Nevraumont Jan 30 '21 at 17:54
  • Thanks a lot ... but I think I won't implement something in std-namespace (afaik C++ standard disallows it). So I think i will bite the bullet and implement a function and use it wherever it doesn't compile after replacing my types with structs. – Zoppo Jan 31 '21 at 08:58
  • 1
    @zoppo You specialziaing certain traits for types dependent on your types is explicitly allowed and by design. It is how you are supposed to do it. – Yakk - Adam Nevraumont Jan 31 '21 at 13:10
1

I started from splitting the instruction into two parts (with the old-style initialization to bypass any problems related to std::initailizer_list, if any):

 s_int_triple s( 0, 0, 0 );
 auto [b1, b2, b3] = s;

The compiler error reads:

main.cpp:27:10: error: cannot decompose class type ‘std::_Tuple_impl<1, int, int>’: its base classes ‘std::_Head_base<2, int, false>’ and ‘std::_Head_base<1, int, false>’ have non-static data members
   27 |     auto [b1, b2, b3] = s;
      |          ^~~~~~~~~~~~

So I conclude that the inheritance of a constructor is not a problem, the problem is with the structured binding. C++ is an awful language when one has to look at its standard template library, but after loading the program to a decent IDE I found that in the gcc 10,2 implementation of std::tuple, this class inherits publicly from _Tuple_impl<0, _Elements...> and privatly from _Head_base<_Idx, _Head> (multiple inheritance, recursive in the number of parameters). Now it's time to look for non-static data members of these classes. The first class looks so messy that I failed. The second, however, has this line in its declaration:

_Head _M_head_impl;

Bingo! This is a non-static data member.

Now its time to find the appropriate rule of structured binding decomposition. We go to https://en.cppreference.com/w/cpp/language/structured_binding and there, straight to "Case 3":

Every non-static data member of E must be a direct member of E or the same base class of E, and must be well-formed in the context of the structured binding when named as e.name. E may not have an anonymous union member. The number of identifiers must equal the number of non-static data members.

So here we have the answer: your class that derives from std::tuple inherits form several other classes, too, and some of them define nonstatic members.

Simple example of this phenomenon:

struct A
{
  int x;
};

struct B: public A
{
  int y;
};

int main()
{
  B obj;
  auto [a, b] = obj;
}

leads to

main2.cpp:14:8: error: cannot decompose class type ‘B’: both it and its base class ‘A’ have non-static data members

So now we come to a real puzzle: if std::tuple inherits from different classes, and each defines a nonstatic member, then why can we use structured binding for tuples? We go back to the cppreference link and see that std::tuple is an exception to Case 3 described above. Compilers have to treat standard tuples and similar classes in their own, special way. In other words, std::tuple and tuple-like classes are handled by Case 2, but any other class or struct is handled by far more restrictive (and very general) Case 3.

So, to make your program compile, you have to make your class "tuple-like", as described in the source cited above. I have no idea if it is possible at all - I guess this would deserve asking a separate question. How to do it is described in the answer by @yakk-adam-nevraumont

zkoza
  • 2,644
  • 3
  • 16
  • 24