Empty nested tuples error

隐身守侯 提交于 2019-12-01 05:23:18

Looks like you found a bug in libstdc++! (This code works in clang with libc++). A reduced test case:

#include <tuple>

int main(){
    auto b = std::tuple<std::tuple<std::tuple<>>>{};
}

The problem is due to how std::tuple is implemented in libstdc++. The tuple implementation uses "recursion" with multiple-inheritance. You can think of tuple<X, Y, Z> as inheriting from both X and tuple<Y, Z>. This means tuple<tuple<>> will inherit from both tuple<> and tuple<> and that will cause an ambiguous base error. Of course the real problem isn't like this, because tuple<tuple<>> doesn't produce any error.

The real implementation that caused the error is like this:

template<size_t _Idx, typename _Head>
struct _Head_base : public _Head
{};

template<size_t _Idx, typename... _Elements>
struct _Tuple_impl;

template<size_t _Idx>
struct _Tuple_impl<_Idx> {};

template<size_t _Idx, typename _Head, typename... _Tail>
struct _Tuple_impl<_Idx, _Head, _Tail...>
    : public _Tuple_impl<_Idx + 1, _Tail...>,
      private _Head_base<_Idx, _Head>
{
    typedef _Tuple_impl<_Idx + 1, _Tail...> _Inherited;
    constexpr _Tuple_impl() = default;
    constexpr _Tuple_impl(_Tuple_impl&& __in) : _Inherited(std::move(__in)) {}
};

template<typename... _Elements>
struct tuple : public _Tuple_impl<0, _Elements...> {};

When we instantiate tuple<tuple<tuple<>>>, we get this inheritance hierarchy:

We see that _Tuple_impl<1> is reachable in two different paths. This is not yet the problem, the problem is in the move constructor, who invokes the move-conversion constructor of _Tuple_impl<1>. Which _Tuple_impl<1> do you want? The compiler doesn't know, so it chooses the give up.

(In your case it's because of _Head_base<0, tuple<>> as you are instantiating tuple<tuple<>, tuple<tuple<>>> instead, but the principle is the same.)


Why libc++ does not have the same problem? There are two main reasons:

  1. tuple<T...> in libc++ use composition instead of inheritance to refer to __tuple_impl<...>.
  2. As a result, the empty base class optimization in __tuple_leaf<tuple<tuple<>>> does not kick in, i.e. __tuple_leaf<tuple<tuple<>>> won't inherit from tuple<tuple<>>
  3. Therefore, the ambiguous base class problem won't happen.
  4. (and each base is unique as mentioned by @mitchnull, but that is not a main difference here.)

As we can see above, if tuple<...> uses inheritance instead of composition, OP's tuple<tuple<>, tuple<tuple<>>> will still inherit from __tuple_leaf<0, tuple<>> twice, which might be a problem.

By the way, for those who have to use gcc, let me give you a quick and dirty fix (for 4.8.0, already submitted a bug report) :

The solution is a small modification of __empty_not_final in the tuple implementation, to prevent empty base optimisation for tuple<> type :

template<typename _Tp>
    using __empty_not_final
      = typename conditional<__is_final(_Tp)||is_same<_Tp,tuple<>>::value,
false_type, is_empty<_Tp>>::type;

instead of

template<typename _Tp>
    using __empty_not_final
      = typename conditional<__is_final(_Tp), false_type, is_empty<_Tp>>::type;

(Note that, this is only an adhoc solution for tuple<> type, it does not solve the real problem described by KennyTM, i.e. struct A{}; auto d = std::tuple<std::tuple<std::tuple<A, A>, A>, A>{}; still does not compile)

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!