2012-06-12 12 views
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 
} 

Dlaczego linia 1 daje błąd kompilacji, a wiersz 2 kompiluje poprawnie? (testowane zarówno w Gcc & Clang)Puste zagnieżdżone błędy krotek

Czy istnieje możliwe obejście tego problemu?

komunikat o błędzie dla brzękiem

/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. 
+0

Czy to może być błąd w Gcc czy Clangu? Twój kod kompiluje się z Visual Studio 2010. Przy okazji, deklarujesz bt dwa razy, czy to jest zamierzone? –

+1

Czy można również wskazać komunikat o błędzie kompilacji? –

+0

Przepraszam, buggy Line 1 został skomentowany. Zapomniałem zmienić nazwę, teraz edytowałem. – dunedain

Odpowiedz

12

Wygląda na to, że znalazłeś błąd w libstdC++! (Ten kod działa w języku clang z libC++). Zredukowana przypadek testowy:

#include <tuple> 

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

Problemem jest to, ze względu na to, jak std::tuple jest realizowany w libstdC++. Implementacja krotki używa "rekursji" z wieloma dziedziczeniami. Możesz myśleć o tuple<X, Y, Z> dziedzicząc po obu X i tuple<Y, Z>. Oznacza to, że tuple<tuple<>> odziedziczy po obu tuple<> i tuple<> i spowoduje to niejednoznaczny błąd podstawowy. Oczywiście prawdziwy problem nie jest taki, ponieważ tuple<tuple<>> nie powoduje żadnych błędów.

Rzeczywista realizacja że przyczyną błędu jest tak:

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

Kiedy instancji tuple<tuple<tuple<>>>, mamy tę hierarchię dziedziczenia:

inheritance diagram of <code>tuple<tuple<tuple<>>></code> in libstdc++

widzimy, że _Tuple_impl<1> jest osiągalny w dwóch różne ścieżki. To nie jest jeszcze problem, problem tkwi w konstruktorze move, który wywołuje konstruktor konwersji ruchu z _Tuple_impl<1>. Którego _Tuple_impl<1> chcesz? Kompilator nie wie, więc wybiera rezygnację.

(W twoim przypadku to z powodu _Head_base<0, tuple<>> jak jesteś instancji tuple<tuple<>, tuple<tuple<>>> zamiast, ale zasada jest taka sama.)


Dlaczego libC++ nie ma ten sam problem? Istnieją dwa główne powody:

  1. tuple<T...> w libc użytku ++ składzie zamiast dziedziczenia odnieść się do __tuple_impl<...>.
  2. W efekcie pusty optymalizacja klasy bazowej w __tuple_leaf<tuple<tuple<>>> nie kopać, tj __tuple_leaf<tuple<tuple<>>> nie będą dziedziczyć tuple<tuple<>>
  3. Dlatego niejednoznaczne problemem klasa bazowa nie stanie.
  4. (a każda baza jest wyjątkowy, ponieważ wymienić @mitchnull, ale nie jest główną różnicą.)

inheritance diagram of <code>tuple<tuple<tuple<>>></code> in libc++

Jak można zobaczyć powyżej, jeżeli tuple<...> wykorzystuje dziedziczenie zamiast kompozycji Wersja OP tuple<tuple<>, tuple<tuple<>>> nadal będzie dziedziczyć po __tuple_leaf<0, tuple<>>, co może być problemem.

+0

Widzę, czy libC++ używa nierekurencyjnej implementacji krotki? – dunedain

+0

@dunedain: Recursive. Zobacz aktualizację. – kennytm

+0

dziękuję za jasną i szczegółową odpowiedź. – dunedain

0

Przy okazji, dla tych, którzy muszą korzystać z gcc, pozwól mi dać szybkie i brudną fix (dla 4.8.0, już składać raport o błędzie):

Rozwiązaniem jest niewielka modyfikacja z __empty_not_final w realizacji krotki, aby zapobiec pusty optymalizacji bazowej za krotki <> Typ:

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

zamiast

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

(Należy zauważyć, że jest to tylko rozwiązanie ad hoc dla krotki <> typ, nie rozwiązuje prawdziwego problemu opisanego przez KennyTM, tj. struct A{}; auto d = std::tuple<std::tuple<std::tuple<A, A>, A>, A>{}; nadal nie kompiluje)