更新时间:2022-10-19 15:14:46
看起来你在libstdc ++中发现了一个错误! (这段代码与libc ++一起工作)。减少的测试用例:
#include< tuple>
int main(){
auto b = std :: tuple< std :: tuple< std :: tuple<>>
}
如何 std :: tuple
在libstdc ++中实现。元组实现使用具有多重继承的递归。您可以将 X
和继承自
tuple< X,Y,Z& tuple< Y,Z>
。这意味着 tuple< tuple<>>
将继承 tuple<>
和 tuple<>
,这将导致不明确的基本错误。当然真正的问题不是这样,因为 tuple< tuple<>>
不会产生任何错误。
导致错误的真正实现是这样的:
模板< 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 ...> _遗传;
constexpr _Tuple_impl()= default;
constexpr _Tuple_impl(_Tuple_impl&& __in):_Inherited(std :: move(__ in)){}
};
template< typename ... _Elements>
struct tuple:public _Tuple_impl< 0,_Elements ...> {};
当我们实例化 tuple< tuple< tuple<>>
,我们得到这个继承层次结构:
中的< code> tuple< tuple<>>>< / code>的继承图我们看到
_Tuple_impl< 1>
在两个不同的路径中可达。这还不是问题,问题是在移动构造函数,他调用 _Tuple_impl< 1>
的移动转换构造函数。 _Tuple_impl< 1>
你想要吗?编译器不知道,所以它选择放弃。 (在你的case是因为 _Head_base< 0,tuple< < tuple<>,< tuple<>>
,但原理是)
为什么libc ++不会有同样的问题?有两个主要原因:
tuple< T ...>
libc ++使用组合而不是继承来引用 __ tuple_impl< ...>
。 __ tuple_leaf< tuple< tuple<>>
不会踢入,即 __ tuple_leaf< tuple< tuple&
不会继承 tuple< tuple<>>
(
>
正如我们可以看到的,如果 tuple& / code>使用继承而不是组合,OP的
两次,这可能是一个问题。 tuple< tuple<>,tuple< tuple<>>
$ c> __ tuple_leaf< 0,tuple<>
#include <iostream>
#include <tuple>
int main(){
auto bt=std::make_tuple(std::tuple<>(),std::tuple<std::tuple<>>()); //Line 1
auto bt2=std::make_tuple(std::tuple<>(),std::tuple<>()); //Line 2
}
Why does Line 1 gives a compile error while Line 2 compiles fine? (tested in both Gcc&Clang)
Is there a possible workaround?
error message for clang
/usr/include/c++/4.6/tuple:150:50: error: ambiguous conversion from derived class 'std::_Tuple_impl<0, std::tuple<>,
std::tuple<std::tuple<> > >' to base class 'std::_Head_base<0, std::tuple<>, true>':
struct std::_Tuple_impl<0, class std::tuple<>, class std::tuple<class std::tuple<> > > -> _Tuple_impl<0UL + 1, class std::tuple<class std::tuple<> > > -> _Head_base<1UL, class std::tuple<class std::tuple<> >, std::is_empty<class tuple<class tuple<> > >::value> -> class std::tuple<class std::tuple<> > -> _Tuple_impl<0, class std::tuple<> > -> _Head_base<0UL, class std::tuple<>, std::is_empty<class tuple<> >::value>
struct std::_Tuple_impl<0, class std::tuple<>, class std::tuple<class std::tuple<> > > -> _Head_base<0UL, class std::tuple<>, std::is_empty<class tuple<> >::value>
_Head& _M_head() { return _Base::_M_head(); }
^~~~~
/usr/include/c++/4.6/tuple:173:33: note: in instantiation of member function 'std::_Tuple_impl<0, std::tuple<>,
std::tuple<std::tuple<> > >::_M_head' requested here
_Base(std::forward<_Head>(__in._M_head())) { }
^
/usr/include/c++/4.6/tuple:334:9: note: in instantiation of member function 'std::_Tuple_impl<0, std::tuple<>,
std::tuple<std::tuple<> > >::_Tuple_impl' requested here
: _Inherited(static_cast<_Inherited&&>(__in)) { }
^
gcc_bug.cpp:5:10: note: in instantiation of member function
'std::tuple<std::tuple<>, std::tuple<std::tuple<> > >::tuple' requested here
auto bt=std::make_tuple(std::tuple<>(),std::tuple<std::tuple<>>());
^
1 error generated.
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:
tuple<T...>
in libc++ use composition instead of inheritance to refer to __tuple_impl<...>
.__tuple_leaf<tuple<tuple<>>>
does not kick in, i.e. __tuple_leaf<tuple<tuple<>>>
won't inherit from tuple<tuple<>>
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.