Programma compilato in modo diverso nei 3 principali compilatori C ++. Qual è giusto?


116

Come seguito interessante (non di grande importanza pratica) alla mia domanda precedente: perché il C ++ ci consente di racchiudere il nome della variabile tra parentesi quando si dichiara una variabile?

Ho scoperto che combinare la dichiarazione tra parentesi con la caratteristica del nome della classe iniettata può portare a risultati sorprendenti riguardo al comportamento del compilatore.

Dai un'occhiata al seguente programma:

#include <iostream>
struct B
{
};

struct C
{
  C (){ std::cout << "C" << '\n'; }
  C (B *) { std::cout << "C (B *)" << '\n';}
};

B *y = nullptr;
int main()
{
  C::C (y);
}
  1. La compilazione con g ++ 4.9.2 mi dà il seguente errore di compilazione:

    main.cpp:16:10: error: cannot call constructor 'C::C' directly [-fpermissive]
  2. Si compila correttamente con MSVC2013 / 2015 e stampa C (B *)

  3. Si compila correttamente con clang 3.5 e stampa C

Quindi la domanda obbligatoria è quale sia quella giusta? :)

(Ho fortemente oscillato verso la versione clang e il modo in cui msvc smette di dichiarare la variabile dopo aver cambiato solo tipo con tecnicamente il suo typedef sembra un po 'strano)


3
C::C y;non ha senso, vero? Nemmeno C::C (y); all'inizio ho pensato che fosse un'istanza di Most-Vexing-Parse stackoverflow.com/questions/tagged/most-vexing-parse , ma ora penso che sia solo un comportamento indefinito, il che significa che tutti e tre i compilatori sono "giusti".
Dale Wilson

4
# 3 clang è decisamente sbagliato, # 2 msvc è troppo permissivo e # 1 g ++ è giusto ((immagino)

8
C::Cnon nomina un tipo, nomina una funzione, quindi GCC ha ragione.
Galik


Risposte:


91

GCC è corretto, almeno secondo le regole di ricerca di C ++ 11. 3.4.3.1 [class.qual] / 2 specifica che, se l'identificatore del nome annidato è lo stesso del nome della classe, si riferisce al costruttore e non al nome della classe inserito. Fornisce esempi:

B::A ba;           // object of type A
A::A a;            // error, A::A is not a type name
struct A::A a2;    // object of type A

Sembra che MSVC lo interpreti erroneamente come espressione cast in stile funzione creando un parametro temporaneo Ccon ycome costruttore; e Clang lo interpreta erroneamente come una dichiarazione di una variabile chiamata ydi tipo C.


2
Sì, 3.4.3.1/2 è la chiave. Buon lavoro!
Gare di leggerezza in orbita

Dice "In una ricerca in cui i nomi delle funzioni non vengono ignorati". Mi sembra che negli esempi forniti, in particolare A::A a;, i nomi delle funzioni debbano essere ignorati o no?
Columbo

1
Seguendo la numerazione in N4296, la chiave è in realtà 3.4.3.1/2.1: "se il nome specificato dopo l'identificatore-nome-annidato, quando cercato in C, è il nome-classe-iniettato di C [...] il nome è invece considerato per nominare il costruttore della classe C. " Il riepilogo di Mike è un po 'troppo semplificato, ad esempio, un typedef del nome della classe all'interno della classe consentirebbe a un identificatore di nome annidato diverso dal nome della classe di fare ancora riferimento al nome della classe, quindi farebbe comunque riferimento al ctor.
Jerry Coffin

2
@Mgetz: Dalla domanda: "Si compila con successo con MSVC2013 / 2015 e stampa C (B *)" .
Gare di leggerezza in orbita

2
Per completezza questo dovrebbe chiarire se è mal formato con diagnosi richiesta, o mal formato senza diagnosi richiesta. In quest'ultimo caso, tutti i compilatori sono "corretti".
MM

16

G ++ è corretto in quanto dà un errore. Perché il costruttore non può essere chiamato direttamente in tale formato senza newoperatore. E anche se il codice chiama C::C, sembra una chiamata al costruttore. Tuttavia, secondo lo standard C ++ 11 3.4.3.1, questa non è una chiamata di funzione legale o un nome di tipo ( vedere la risposta di Mike Seymour ).

Clang è sbagliato poiché non chiama nemmeno la funzione corretta.

MSVC è qualcosa di ragionevole, ma ancora non segue lo standard.


2
Cosa newcambia l'operatore?
Neil Kirk

1
@ NeilKirk: Moltissimo, per le persone che pensano che new B(1,2,3)sia una sorta di "chiamata diretta al costruttore" (che, ovviamente, non lo è) distinta dall'istanza temporanea B(1,2,3)o dalla dichiarazione B b(1,2,3).
Gare di leggerezza in orbita

@LightningRacisinObrit Come descriveresti cosa new B(1,2,3)è?
user2030677

1
@ user2030677: una nuova espressione, utilizzando la parola chiave new, un nome di tipo e un elenco di argomenti del costruttore. Non è ancora una "chiamata diretta al costruttore".
Gare di leggerezza in orbita

"Clang è sbagliato poiché non chiama nemmeno la funzione corretta.": Penso (perché l'osservazione dell'OP sulle parentesi nelle dichiarazioni) che Clang interpreta C::C (y); come C::C y;, cioè una definizione di una variabile y di tipo C (usando il tipo iniettato C: : C ignorando erroneamente 3.4.1,2 la specifica del linguaggio sempre più folle che fa di C :: C il costruttore). Non è proprio un errore lampante come sembri pensare, imo.
Peter - Ripristina Monica il
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.