6
#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
}

行2が正常にコンパイルされているのに、行1でコンパイルエラーが発生するのはなぜですか?(GccとClangの両方でテスト済み)

考えられる回避策はありますか?

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.
4

2 に答える 2

12

libstdc++ にバグが見つかったようです。(このコードは libc++ の clang で動作します)。簡略化されたテスト ケース:

#include <tuple>

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

問題はstd::tuple、libstdc++ での実装方法によるものです。タプルの実装では、多重継承による「再帰」が使用されます。tuple<X, Y, Z>と の両方から継承しているXと考えることができますtuple<Y, Z>。これは、 and とandtuple<tuple<>>の両方から継承することを意味し、あいまいな基本エラーが発生します。もちろん、実際の問題はこのようなものではありません。エラーが発生しないためです。tuple<>tuple<>tuple<tuple<>>

エラーの原因となった実際の実装は次のようになります。

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...> {};

をインスタンス化するtuple<tuple<tuple<>>>と、次の継承階層が得られます。

libstdc++ の <code>tuple<tuple<tuple<>>></code> の継承図

_Tuple_impl<1>2 つの異なるパスで到達可能であることがわかります。これはまだ問題ではありません。問題は、 の move-conversion コンストラクタを呼び出す move コンストラクタにあり_Tuple_impl<1>ます。あなたはどれ_Tuple_impl<1>が欲しいですか?コンパイラは知らないので、諦めます。

(あなたの場合、代わりに_Head_base<0, tuple<>>インスタンス化しているためですが、原則は同じです。)tuple<tuple<>, tuple<tuple<>>>


libc++ に同じ問題がないのはなぜですか? 主な理由は 2 つあります。

  1. tuple<T...>libc++ では、継承の代わりに構成を使用して を参照し__tuple_impl<...>ます。
  2. その結果、空の基本クラスの最適化は開始され__tuple_leaf<tuple<tuple<>>>ません。つまり、__tuple_leaf<tuple<tuple<>>>から継承されません。tuple<tuple<>>
  3. したがって、あいまいな基本クラスの問題は発生しません。
  4. (そして、@mitchnull で言及されているように、各ベースは一意ですが、ここでは主な違いではありません。)

libc++ の <code>tuple<tuple<tuple<>>></code> の継承図

上で見たようにtuple<...>、コンポジションの代わりに継承を使用すると、OPtuple<tuple<>, tuple<tuple<>>>は依然として__tuple_leaf<0, tuple<>>2 回継承するため、問題になる可能性があります。

于 2012-06-12T15:09:21.867 に答える
0

ところで、gcc を使用しなければならない人のために、簡単で汚い修正を提供させてください (4.8.0 については、既にバグ レポートを提出しています)。

解決策は、タプルの実装で __empty_not_final を少し変更して、 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;

それ以外の

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

(これは tuple<> 型のアドホックな解決策にすぎないことに注意してください。KennyTM で説明されている実際の問題は解決されません。つまり、struct A{}; auto d = std::tuple<std::tuple<std::tuple<A, A>, A>, A>{};まだコンパイルされません)

于 2012-06-12T17:28:53.710 に答える