11

Który konstruktor powinien zostać wywołany w poniższym kodzie i dlaczego?Inicjalizacja podwójnego nawiasu klamkowego

struct S 
{ 
    int i; 
    S() = default; 
    S(void *) : i{1} { ; } 
}; 

S s{{}}; 

Jeśli użyję clang (z bagażnika), to zostanie wywołana druga.

Jeśli drugi konstruktor zostanie skomentowany, to S{{}} jest nadal poprawnym wyrażeniem, ale (jak sądzę) konstruktor ruchu z domyślnie zbudowanego wystąpienia S{} jest wywoływany w tym przypadku.

Dlaczego konstruktor konwersji ma pierwszeństwo w stosunku do domyślnego konstruktora konwersji?

Zamiarem takiej kombinacji konstruktorów o numerze S jest zapisanie jej właściwości std::is_trivially_default_constructible_v<S>, z wyjątkiem skończonego zestawu przypadków, gdy powinna ona zostać zainicjowana w określony sposób.

+0

"* Jeśli drugi konstruktor zostanie skomentowany, to S {{}} jest nadal poprawnym wyrażeniem, ale (na pewno) konstruktor ruchu z konstruowanej domyślnie instancji S {} jest wywoływany w przypadku. *" Nie , agreguje-inicjuje 'S' za pomocą polecenia brace-init'd' int'. – ildjarn

+0

@ildjarn Pytanie wciąż obowiązuje. – Orient

Odpowiedz

9

Jeśli drugi konstruktor komentarzem, to S {{}} jest nadal ważna ekspresji, ale (pewno) przemieszczają się, konstruktor domyślny skonstruowany przykład S {} jest wywoływana w przypadku.

W rzeczywistości tak nie jest. Kolejność w [dcl.init.list] jest:

Lista inicjalizacja obiektu lub odniesienia typu T jest zdefiniowany następująco:
- Jeśli T jest agregat klasa a lista inicjator ma pojedynczy element typu cv U, [...]
- W przeciwnym razie, jeśli T jest tablicą znaków i [...]
- W przeciwnym razie, jeśli T jest agregatem, przeprowadzana jest inicjalizacja agregacyjna (8.6.1).

Po usunięciu konstruktora S(void *), S staje agregatem - nie ma konstruktora użytkownika warunkiem. S() = default nie jest liczony jako dostarczony przez użytkownika z przyczyn. Inicjalizacja agregacji z {} zakończy się inicjowaniem wartości elementu i.


Dlaczego konstruktor konwersja ma wyższy priorytet niż domyślny w pierwszym przypadku?

Z void* pozostałego, trzymajmy zejście listę wypunktowania:

- W przeciwnym wypadku, gdy lista inicjator ma żadnych elementów [...]
- W przeciwnym razie, jeśli T jest specjalizacja z std :: initializer_list, [...]
- W przeciwnym razie, jeśli T jest typem klasy, rozważane są konstruktorzy. Odpowiednie konstruktory są wymienione na , a najlepsze wybiera się poprzez rozdzielczość przeciążenia (13.3, 13.3.1.7).

[over.match.list] daje dwufazowej Proces rozwiązywania przeciążeniowe

- początkowo funkcje kandydujące inicjatora-lista konstruktory (8.6.4) listy argumentów klasy T i składa się z listy inicjalizatorów jako pojedynczego argumentu.
- Jeśli nie zostanie znaleziony wykonalny konstruktor listy inicjalizującej, ponownie zostanie wykonana rezerwa przeciążania, gdzie funkcje kandydujące są wszystkie konstruktorami klasy T, a lista argumentów składa się z elementów z listy inicjalizatora.

Jeśli na liście inicjalizacyjnej nie ma elementów, a znak T ma domyślny konstruktor, pominięto pierwszą fazę.

S nie ma żadnych initializer lista konstruktorów, więc idziemy do drugiej kuli i wyliczyć wszystkie konstruktorów z listy argument {}. Mamy wiele żywotnych konstruktory:

S(S const&); 
S(S&&); 
S(void *); 

Sekwencje konwersji są zdefiniowane w [over.ics.list]

przeciwnym razie, jeśli nie jest parametrem kruszywa klasy X i przeciążenie rozdzielczości na 13,3 .1.7 wybiera pojedynczego najlepszego konstruktora C z X, aby wykonać inicjalizację obiektu typu X z listy inicjalizatora argumentów:
- Jeśli C nie jest konstruktorem listy inicjalizacyjnej, a lista inicjalizacyjna ma pojedynczy element typu cv U, [...] - W przeciwnym razie niejawna sekwencja konwersji jest zdefiniowana przez użytkownika sekwencja konwersji z drugą standardową sekwencją konwersji i konwersja tożsamości.

i

W przeciwnym razie, jeśli typ parametru nie jest klasą: [...] - jeśli lista inicjator ma elementy, niejawna konwersja sekwencja jest konwersja tożsamość.

Oznacza to, że konstruktorzy S(S&&) i S(S const&) są zarówno zdefiniowanymi przez użytkownika sekwencjami konwersji, jak i konwersją tożsamości. Ale S(void *) to tylko konwersja tożsamości.

Ale [over.best.ics] ma tę dodatkową zasadę:

Jednakże, jeśli cel jest
- pierwszy parametr konstruktora lub
- niejawny parametr przedmiot zdefiniowana przez użytkownika funkcja konwersji i konstruktor lub zdefiniowana przez użytkownika funkcja konwersji jest kandydatem
- 13.3.1.3, gdy [...]
- 13.3.1.4, 13.3.1.5 lub 13.3.1.6 (we wszystkich przypadkach), lub
- druga faza 13.3.1.7 gdy lista inicjatora ma dokładnie jeden element, który sam nie jest lista inicjator, a celem jest pierwszym parametrem konstruktora klasy X i konwersji do X lub w odniesieniu do (Prawdopodobnie cv-kwalifikowany) X,

zdefiniowane przez użytkownika sekwencje konwersji nie są brane pod uwagę.

Wyklucza to z uwagi S(S const&) i S(S&&) jako kandydaci - są to właśnie w tym przypadku - celem jest pierwszym parametrem konstruktora wyniku drugiego etapu [over.match.list] i cel będący odniesieniem do potencjalnie kwalifikowanego cv S, a taka sekwencja konwersji byłaby zdefiniowana przez użytkownika.

Dlatego jedynym pozostałym kandydatem jest S(void *), więc jest to banalnie najlepszy kandydat.

+0

@ T.C Sądzę, że nie byłem pewien, czy drugi był standardową sekwencją konwersji. Pierwsza jest wyraźnie określona jako zdefiniowana przez użytkownika ... – Barry

+3

Twoja analiza jest poprawna, z wyjątkiem drobnego szczegółu: Konstruktorzy ruchu i kopiowania nie są wykonalni z powodu 16.3.3.1 [nad.best.ics] p4 "i konstruktora lub użytkownika -definiowana funkcja konwersji jest kandydatem do ... drugiej fazy [over.match.list], gdy lista inicjalizatorów ma dokładnie jeden element, który sam jest listą inicjalizacyjną, a target jest pierwszym parametrem konstruktora klasy X , a konwersja to X lub odniesienie do cv X ... nie są brane pod uwagę zdefiniowane przez użytkownika sekwencje konwersji. " –

+0

Gdyby nie usunął konstruktora dostarczonego przez użytkownika (na przykład poprzez określenie definicji domyślnego konstruktora), jego przykład z usuniętym konstruktorem konwersji nie byłby już kompilowany. –