Programme compilé différemment dans 3 compilateurs C ++ majeurs. Laquelle est la bonne?

116

Comme suivi intéressant (pas d'une grande importance pratique cependant) à ma question précédente: Pourquoi C ++ nous permet-il d'entourer le nom de la variable entre parenthèses lors de la déclaration d'une variable?

J'ai découvert que la combinaison de la déclaration entre parenthèses avec la fonction de nom de classe injectée peut conduire à des résultats surprenants concernant le comportement du compilateur.

Jetez un œil au programme suivant:

#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 compilation avec g ++ 4.9.2 me donne l'erreur de compilation suivante:

    main.cpp:16:10: error: cannot call constructor 'C::C' directly [-fpermissive]
  2. Il se compile avec succès avec MSVC2013 / 2015 et imprime C (B *)

  3. Il se compile avec succès avec clang 3.5 et imprime C

La question obligatoire est donc laquelle a raison? :)

(Je me suis fortement incliné vers la version clang et la manière msvc d'arrêter de déclarer une variable après avoir simplement changé de type avec techniquement son typedef semble un peu bizarre)

Predelnik
la source
3
C::C y;n'a pas de sens, non? Ni ne C::C (y); Au début , je pensais que c'était une instance de plus-vexatoire-Parse stackoverflow.com/questions/tagged/most-vexing-parse , mais maintenant je pense que ce comportement est tout simplement indéfini qui signifie trois compilateurs sont « droit ».
Dale Wilson
4
# 3 clang est définitivement faux, # 2 msvc est trop permissif et # 1 g ++ a raison ((je suppose)
8
C::Cne nomme pas un type, il nomme une fonction, donc GCC a raison imo.
Galik
11
Classé comme un bug clang .
Cornstalks

Réponses:

91

GCC est correct, du moins selon les règles de recherche C ++ 11. 3.4.3.1 [class.qual] / 2 spécifie que, si le spécificateur de nom imbriqué est le même que le nom de classe, il fait référence au constructeur et non au nom de classe injecté. Il donne des exemples:

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

Il semble que MSVC l'interprète mal comme une expression de conversion de style fonction créant un temporaire Cavec ycomme paramètre de constructeur; et Clang l'interprète mal comme une déclaration d'une variable appelée yde type C.

Mike Seymour
la source
2
Oui, 3.4.3.1/2 est la clé. Bon travail!
Courses de légèreté en orbite le
Il dit "Dans une recherche dans laquelle les noms de fonction ne sont pas ignorés". Il me semble que dans les exemples donnés, en particulier A::A a;, les noms de fonctions devraient être ignorés - ou pas?
Columbo
1
En suivant la numérotation en N4296, la clé est vraiment 3.4.3.1/2.1: "si le nom spécifié après le spécificateur de nom imbriqué, lorsqu'il est recherché en C, est le nom-classe-injecté de C [...] le nom est plutôt considéré comme le nom du constructeur de la classe C. " Le résumé de Mike est cependant un peu trop simplifié - par exemple, un typedef du nom de classe à l'intérieur de la classe permettrait à un spécificateur de nom imbriqué différent du nom de classe de faire toujours référence au nom de classe, donc il ferait toujours référence au ctor.
Jerry Coffin
2
@Mgetz: De la question: "Il se compile avec succès avec MSVC2013 / 2015 et imprime C (B *)" .
Courses de légèreté en orbite le
2
Par souci d'exhaustivité, cela devrait préciser s'il est mal formé avec un diagnostic requis, ou mal formé sans aucun diagnostic requis. Si c'est le cas, tous les compilateurs ont "raison".
MM
16

G ++ est correct car il donne une erreur. Parce que le constructeur ne pouvait pas être appelé directement dans un tel format sans newopérateur. Et bien que votre code appelle C::C, cela ressemble à un appel de constructeur. Cependant, selon la norme C ++ 11 3.4.3.1, il ne s'agit pas d'un appel de fonction légal, ni d'un nom de type ( voir la réponse de Mike Seymour ).

Clang a tort car il n'appelle même pas la fonction correcte.

MSVC est quelque chose de raisonnable, mais il ne suit toujours pas la norme.

Kun Ling
la source
2
Que change l' newopérateur?
Neil Kirk
1
@NeilKirk: Beaucoup, pour les gens qui pensent que new B(1,2,3)c'est une sorte d '"appel direct au constructeur" (ce qui, bien sûr, ce n'est pas le cas) par opposition à l'instanciation temporaire B(1,2,3)ou à la déclaration B b(1,2,3).
Courses de légèreté en orbite le
@LightningRacisinObrit Comment décririez-vous ce que new B(1,2,3)c'est?
user2030677
1
@ user2030677: une nouvelle expression, utilisant le mot-clé new, un nom de type et une liste d'arguments de constructeur. Ce n'est toujours pas un "appel direct au constructeur".
Courses de légèreté en orbite le
"Clang a tort car il n'appelle même pas la fonction correcte.": Je pense (parce que la remarque de l'OP sur les parenthèses dans les déclarations) que Clang interprète C::C (y); comme C::C y;, c'est-à-dire une définition d'une variable y de type C (en utilisant le type C injecté: : C en ignorant à tort la spécification de langage de plus en plus insensée 3.4.1,2 qui fait de C :: C le constructeur). Ce n'est pas tout à fait une erreur flagrante comme vous semblez le penser, imo.
Peter - Réintègre Monica le