Programa sendo compilado de forma diferente nos 3 principais compiladores C ++. Qual está certo?


116

Como uma continuação interessante (embora não de grande importância prática) para minha pergunta anterior: Por que C ++ nos permite colocar o nome da variável entre parênteses ao declarar uma variável?

Eu descobri que combinar a declaração entre parênteses com o recurso de nome de classe injetado pode levar a resultados surpreendentes em relação ao comportamento do compilador.

Dê uma olhada no seguinte programa:

#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. Compilar com g ++ 4.9.2 me dá o seguinte erro de compilação:

    main.cpp:16:10: error: cannot call constructor 'C::C' directly [-fpermissive]
  2. Compila com sucesso com MSVC2013 / 2015 e imprime C (B *)

  3. Compila com sucesso com o clang 3.5 e imprime C

Portanto, a pergunta obrigatória é: qual está certo? :)

(No entanto, mudei fortemente para a versão clang e a maneira do msvc de parar de declarar a variável depois de apenas alterar o tipo com tecnicamente seu typedef parece meio estranho)


3
C::C y;não faz sentido, certo? Nem a C::C (y); princípio, pensei que fosse uma instância de Most-Vexing-Parse stackoverflow.com/questions/tagged/most-vexing-parse , mas agora acho que é apenas um comportamento indefinido, o que significa que todos os três compiladores estão "certos".
Dale Wilson

4
# 3 clang está definitivamente errado, # 2 msvc é muito permissivo e # 1 g ++ está certo ((eu acho)

8
C::Cnão nomeia um tipo, ele nomeia uma função, então o GCC está certo.
Galik


Respostas:


91

O GCC está correto, pelo menos de acordo com as regras de pesquisa do C ++ 11. 3.4.3.1 [class.qual] / 2 especifica que, se o especificador de nome aninhado for o mesmo que o nome da classe, ele se refere ao construtor e não ao nome da classe injetada. Ele dá exemplos:

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

Parece que o MSVC o interpreta erroneamente como uma expressão de conversão de estilo de função, criando um temporário Ccom ycomo um parâmetro do construtor; e o Clang o interpreta erroneamente como uma declaração de uma variável chamada yde tipo C.


2
Sim, 3.4.3.1/2 é a chave. Bom trabalho!
Lightness Races in Orbit

Diz "Em uma pesquisa na qual os nomes das funções não são ignorados". Parece-me que nos exemplos dados, em particular A::A a;, os nomes das funções devem ser ignorados - ou não?
Columbo

1
Indo pela numeração em N4296, a chave é realmente 3.4.3.1/2.1: "se o nome especificado após o especificador de nome aninhado, quando procurado em C, é o nome de classe injetado de C [...] o nome é, em vez disso, considerado o nome do construtor da classe C. " O resumo de Mike é um pouco simplificado demais - por exemplo, um typedef do nome da classe dentro da classe permitiria que um especificador de nome aninhado diferente do nome da classe ainda se referisse ao nome da classe, então ainda se referiria ao ctor.
Jerry Coffin

2
@Mgetz: Da pergunta: "Compila com sucesso com MSVC2013 / 2015 e imprime C (B *)" .
Lightness Races in Orbit

2
Para completar, deve esclarecer se está malformado com a necessidade de diagnóstico ou malformado sem necessidade de diagnóstico. Se for o último, então todos os compiladores estão "certos".
MM

16

G ++ está correto porque apresenta um erro. Porque o construtor não pode ser chamado diretamente nesse formato sem o newoperador. E embora seu código chame C::C, parece uma chamada de construtor. No entanto, de acordo com o padrão C ++ 11 3.4.3.1, esta não é uma chamada de função válida ou um nome de tipo ( consulte a resposta de Mike Seymour ).

O Clang está errado, pois nem mesmo chama a função correta.

MSVC é algo razoável, mas ainda não segue o padrão.


2
O que a newoperadora muda?
Neil Kirk

1
@NeilKirk: Muito, para pessoas que pensam que new B(1,2,3)é algum tipo de "chamada direta do construtor" (o que, claro, não é) diferente da instanciação temporária B(1,2,3)ou da declaração B b(1,2,3).
Lightness Races in Orbit

@LightningRacisinObrit Como você descreveria o que new B(1,2,3)é?
user2030677

1
@ user2030677: Uma nova expressão, usando a palavra-chave new, um nome de tipo e uma lista de argumentos do construtor. Ainda não é uma "chamada direta do construtor".
Lightness Races in Orbit

"O Clang está errado, pois ele nem mesmo chama a função correta.": Eu acho (devido à observação do OP sobre parênteses nas declarações) que o Clang interpreta C::C (y); como C::C y;, ou seja, uma definição de uma variável y do tipo C (usando o tipo C injetado: : C enquanto ignora erroneamente a especificação cada vez mais insana da linguagem 3.4.1,2 que torna C :: C o construtor). Esse não é um erro gritante como você parece pensar, imo.
Peter - Reintegrar Monica em
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.