2012-02-15 29 views
9

Ten problem jest oparty na kodzie, który działa dla mnie na GCC-4.6, ale nie dla innego użytkownika z CLang-3.0, zarówno w trybie C++ 0x.Konflikt między konstruktorem kopiowania a konstruktorem przesyłania dalej

template <typename T> 
struct MyBase 
{ 
//protected: 
    T m; 

    template <typename Args...> 
    MyBase(Args&& ...x) : m(std::forward<Args>(x)...) {} 
}; 

Przedmiotem MyBase może przyjąć dowolną listę argumentów konstruktora, tak długo jak T obsługuje że podpis budowlanej. Problem dotyczy funkcji specjalnych członków.

  1. IIUC, szablon konstruktora anuluje automatycznie zdefiniowany domyślny konstruktor. Jednakże, ponieważ szablon może przyjmować zero argumentów, będzie działał jako jawnie zdefiniowany domyślny konstruktor (tak długo, jak T jest domyślnie konstruktywny).
  2. IIUC, określenie "zasady konstruowania kopii" klasy ignoruje szablony konstruktorów. Oznacza to, że w tym przypadku MyBase zyska automatycznie zdefiniowanego konstruktora kopiowania (o ile tylko można będzie kopiować T), który będzie kanałować konstrukcję kopii T.
  3. Zastosuj poprzedni krok również dla konstrukcji ruchu.

Więc jeśli przekazać MyBase<T> const & jako jedyny argument konstruktora, który konstruktor zostanie wywołany, przekazywanie jeden lub niejawne kopiowanie jeden?

typedef std::vector<Int> int_vector; 
typedef MyBase<int_vector> VB_type; 

int_vector a{ 1, 3, 5 }; 
VB_type  b{ a }; 
VB_type  c{ b }; // which constructor gets called 

Problemem mojego użytkownika było użycie tego jako klasy bazowej. Kompilator skarżył się, że jego klasa nie może zsyntetyzować automatycznie zdefiniowanego konstruktora kopii, ponieważ nie może znaleźć dopasowania do szablonu konstruktora klasy podstawowej. Czy nie powinien wywoływać automatycznego konstruktora kopiowania dla swojego własnego automatycznego konstruktora kopiowania? Czy CLang jest błędny z powodu konfliktu?

+0

Interesujące pytanie. Polecam wykonanie sscce (zobacz http://sscce.org/), która działa w GCC i nie działa w CLang, aby pomóc nam lepiej zrozumieć problem i odtworzyć go samodzielnie. Podaj nam dokładny komunikat o błędzie z CLang. –

+2

W rzeczywistości wersja gcc stosunkowo blisko głowy (od 20120202) również nie akceptuje tego kodu. Wygląda na to, że konstruktor przekazujący jest odbierany nawet dla kopii. Nie jestem do końca pewien, dlaczego tak jest. Nie jest to związane z "jednolitą składnią inicjalizacji": nawet przy użyciu nawiasów dla ostatniej deklaracji nie kompiluje się. –

+0

Mój kod jest czymś, co napisałem dekadę temu dla Boost i zaktualizowany dla C++ 11 w zeszłym tygodniu. Nie wiem, jak pracować z całym systemem Boost, ale możesz przejrzeć [moje zmiany] (https://svn.boost.org/trac/boost/changeset/76982) i [obecny stan] (http : //svn.boost.org/svn/boost/trunk/boost/utility/base_from_member.hpp) pliku (w wersji 77031 w tym piśmie). – CTMacUser

Odpowiedz

9

Jestem po prostu w barze z Richardem Cordenem i między nami stwierdziliśmy, że problem nie ma nic wspólnego z variadic lub rvalues. Domyślnie wygenerowany konstrukt kopii w tym przypadku przyjmuje argument MyBase const&. Konstruktor szablonowy wywnioskował typ argumentu jako MyBase&. To jest lepsze dopasowanie, które jest wywoływane, chociaż nie jest konstruktem kopiowania.

Przykładowy kod użyłem do testowania to:

#include <utility> 
#include <vector>i 

template <typename T> 
struct MyBase 
{ 
    template <typename... S> MyBase(S&&... args): 
     m(std::forward<S>(args)...) 
    { 
    } 
    T m; 
}; 

struct Derived: MyBase<std::vector<int> > 
{ 
}; 

int main() 
{ 
    std::vector<int>    vec(3, 1); 
    MyBase<std::vector<int> > const fv1{ vec }; 
    MyBase<std::vector<int> >  fv2{ fv1 }; 
    MyBase<std::vector<int> >  fv3{ fv2 }; // ERROR! 

    Derived d0; 
    Derived d1(d0); 
} 

Musiałem usunąć użycie list initializer bo to nie jest obsługiwany przez brzękiem, jeszcze. Ten przykład kompiluje się z wyjątkiem inicjowania fv3, który kończy się niepowodzeniem: konstruktor kopii zsyntetyzowany dla MyBase<T> pobiera MyBase<T> const& i tym samym przekazuje fv2 wywołania konstruktorowi wariasu przekazując obiekt do klasy bazowej.

Mogłem źle zrozumieć to pytanie, ale oparte na d0 i d1 wydaje się, że zarówno konstruktor domyślny, jak i konstruktor kopii są syntezowane. Jest to jednak z całkiem aktualnymi wersjami gcc i clang. Oznacza to, że nie wyjaśnia, dlaczego nie jest syntetyzowany żaden konstruktor kopii, ponieważ jest on syntetyzowany.

Aby podkreślić, że ten problem nie ma nic wspólnego z listami argumentów variadic lub rvalues: poniższy kod pokazuje problem, w którym wywołany jest szablonowy konstruktor, chociaż wygląda tak, jakby wywoływany był konstruktor kopiowania, a konstruktory kopiowania nigdy nie są szablonami.To jest rzeczywiście nieco zaskakujące zachowanie, które ja zdecydowanie nieświadomi:

#include <iostream> 
struct MyBase 
{ 
    MyBase() {} 
    template <typename T> MyBase(T&) { std::cout << "template\n"; } 
}; 

int main() 
{ 
    MyBase f0; 
    MyBase f1(const_cast<MyBase const&>(f0)); 
    MyBase f2(f0); 
} 

W rezultacie dodanie zmiennej liczbie argumentów konstruktora jak w pytaniu do klasy, która nie ma żadnych innych konstruktorów zmienia konstruktorów zachowanie kopii pracy! Osobiście uważam, że jest to raczej niefortunne. To faktycznie oznacza, że ​​klasa MyBase musi zostać zwiększona z kopiować i przenosić konstruktorów, a także:

MyBase(MyBase const&) = default; 
    MyBase(MyBase&) = default; 
    MyBase(MyBase&&) = default; 

Niestety, to nie wydaje się do pracy z gcc: to narzeka konstruktorów defaulted Copy (ona twierdzi defaulted W definicji klasy nie można zdefiniować konstruktora kopiowania, który przyjmuje referencje nie będące stałymi. Clang akceptuje ten kod bez żadnych skarg. Stosując definicję konstruktora kopii zrobieniu non-const odniesienia współpracuje zarówno z gcc i brzękiem:

template <typename T> MyBase<T>::MyBase(MyBase<T>&) = default; 
+0

@MooingDuck: Nie mogłem odtworzyć problemu z użyciem odpowiedniej bazy klasa (zobacz moją aktualizację odpowiedzi). –

+0

Tak, ja użyłem różnych nazw oryginalnych i nie aktualizowałem wszystkich miejsc. Mam nadzieję, że naprawiłem wszystko teraz. –

1

Ja osobiście miałem problem z GCC migawek od dłuższego czasu. Nie miałem pojęcia, co się dzieje (i jeśli w ogóle było to dozwolone), ale doszedłem do podobnego wniosku, jak Dietmar Kühl: konstruktorzy kopii/ruchu wciąż tu są, ale nie zawsze są preferowani przez mechanikę przeciążania rozkład.

Używam tego obejść problem dla niektórych czas:

// I don't use std::decay on purpose but it shouldn't matter 
template<typename T, typename U> 
using is_related = std::is_same< 
    typename std::remove_cv<typename std::remove_reference<T>::type>::type 
    , typename std::remove_cv<typename std::remove_reference<U>::type>::type 
>; 

template<typename... T> 
struct enable_if_unrelated: std::enable_if<true> {}; 

template<typename T, typename U, typename... Us> 
struct enable_if_unrelated 
: std::enable_if<!is_related<T, U>::value> {}; 

Używanie go z konstruktora jak będzie wyglądać twoja:

template< 
    typename... Args 
    , typename = typename enable_if_unrelated<MyBase, Args...>::type 
> 
MyBase(Args&&... args); 

Niektóre wyjaśnienia są w kolejności . is_related to funkcja binarna, która sprawdza, czy dwa typy są identyczne, niezależnie od specyfikatorów najwyższego poziomu (const, volatile, &, &&). Chodzi o to, że konstruktory, które będą strzeżone przez tę cechę, są "konwertującymi" konstruktorami i nie są zaprojektowane do radzenia sobie z parametrami samego typu klasy, ale tylko wtedy, gdy ten parametr znajduje się na pierwszej pozycji. Konstrukcja z parametrami np. (std::allocator_arg_t, MyBase) będzie w porządku.

Teraz miałem też enable_if_unrelated jako binarne metafunkcje, ale ponieważ bardzo wygodne jest, aby doskonale przekierowujące konstruktory variadyczne działały również w przypadku zerowym, przeprojektowałem je tak, aby przyjmowało dowolną liczbę argumentów (chociaż można je zaprojektować zaakceptować przynajmniej jeden argument, rodzaj klasy konstruktora, którego strzegamy). Oznacza to, że w naszym przypadku, jeśli konstruktor jest wywoływany bez argumentów, to nie jest SFINAEd. W przeciwnym razie musisz dodać deklarację MyBase() = default;.

Wreszcie, jeśli konstruktor przekazuje dane do bazy, alternatywą jest dziedziczenie konstruktora tej bazy (tj. using Base::Base;). Tak nie jest w twoim przykładzie.

0

Podniosłem decyzję Dietmara, ponieważ całkowicie się z nim zgadzam. Ale chcę podzielić „rozwiązanie” używałem jakiś czas wcześniej, aby uniknąć tych problemów:

ja celowo dodawane obojętne parametr do zmiennej liczbie argumentów konstruktora:

enum fwd_t {fwd}; 

template<class T> 
class wrapper 
{ 
    T m; 
public: 
    template<class...Args> 
    wrapper(fwd_t, Args&&...args) 
    : m(std::forward<Args>(args)...) 
    {} 
}; 

::: 

int main() 
{ 
    wrapper<std::string> w (fwd,"hello world"); 
} 

Zwłaszcza, że ​​konstruktor zaakceptuje niczego bez ten parametr fikcyjny, wydaje się właściwe, aby kod użytkownika jawnie wybrał poprawny konstruktor przez (sortowanie) "nazywanie" go.

Może to nie być możliwe w twoim przypadku. Ale czasami możesz uciec.

Powiązane problemy