Программа компилируется по-разному в трех основных компиляторах C ++. Какой из них правильный?

116

В качестве интересного продолжения (хотя и не имеющего большого практического значения) моего предыдущего вопроса: почему C ++ позволяет нам заключать имя переменной в круглые скобки при объявлении переменной?

Я обнаружил, что объединение объявления в круглых скобках с введенной функцией имени класса может привести к неожиданным результатам в отношении поведения компилятора.

Взгляните на следующую программу:

#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. Компиляция с g ++ 4.9.2 дает мне следующую ошибку компиляции:

    main.cpp:16:10: error: cannot call constructor 'C::C' directly [-fpermissive]
  2. Он успешно компилируется с MSVC2013 / 2015 и печатает C (B *)

  3. Он успешно компилируется с clang 3.5 и печатает C

Поэтому обязательный вопрос: какой из них правильный? :)

(Я сильно склонялся к версии clang, и способ msvc перестать объявлять переменную после простого изменения типа с технически его typedef кажется странным)

Predelnik
источник
3
C::C y;не имеет смысла, правда? И тоже. C::C (y); Сначала я думал, что это экземпляр Most-Vexing-Parse stackoverflow.com/questions/tagged/most-vexing-parse , но теперь я думаю, что это просто неопределенное поведение, означающее, что все три компилятора «правы».
Дейл Уилсон
4
# 3 clang определенно неправильный, # 2 msvc слишком разрешительный, а # 1 g ++ правильный ((я думаю)
8
C::Cне называет тип, он называет функцию, так что GCC прав imo.
Галик

Ответы:

91

GCC правильный, по крайней мере, в соответствии с правилами поиска C ++ 11. 3.4.3.1 [class.qual] / 2 указывает, что если описатель вложенного имени совпадает с именем класса, он относится к конструктору, а не к внедренному имени класса. Приведены примеры:

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

Похоже , MSVC извращает его в качестве выражения приведения функции стиля , создавая временный Cс в yкачестве параметра конструктора; а Clang неверно интерпретирует это как объявление переменной с именем yтипа C.

Майк Сеймур
источник
2
Да, 3.4.3.1/2 - это ключ. Хорошая работа!
Гонки легкости на орбите
Он говорит: «В поиске, в котором имена функций не игнорируются». Мне кажется, что в приведенных примерах, в частности A::A a;, следует игнорировать имена функций - или нет?
Коломбо
1
Судя по нумерации в N4296, ключ на самом деле 3.4.3.1/2.1: «если имя, указанное после спецификатора вложенного имени, при поиске в C, является именем внедренного класса C [...] вместо этого считается, что имя является именем конструктора класса C. " Резюме Майка, тем не менее, немного упрощено - например, определение типа имени класса внутри класса позволит вложенному описателю имени, отличному от имени класса, по-прежнему ссылаться на имя класса, поэтому он все равно будет ссылаться на т е р.
Джерри Коффин
2
@Mgetz: Из вопроса: "Он успешно компилируется с MSVC2013 / 2015 и печатает C (B *)" .
Гонки легкости на орбите
2
Для полноты изложения следует прояснить, является ли он плохо сформированным и не требует диагностики. В последнем случае все компиляторы «правы».
MM
16

G ++ правильный, так как выдает ошибку. Потому что конструктор не может быть вызван напрямую в таком формате без newоператора. И хотя ваш код вызывает C::C, это похоже на вызов конструктора. Однако в соответствии со стандартом C ++ 11 3.4.3.1 это не законный вызов функции или имя типа ( см. Ответ Майка Сеймура ).

Clang ошибочен, поскольку он даже не вызывает правильную функцию.

MSVC - это что-то разумное, но все же оно не соответствует стандарту.

Кун Лин
источник
2
Что newменяет оператор?
Нил Кирк
1
@NeilKirk: Много, для людей, которые думают, что new B(1,2,3)это своего рода «прямой вызов конструктора» (что, конечно, не так), в отличие от временного экземпляра B(1,2,3)или объявления B b(1,2,3).
Гонки легкости на орбите
@LightningRacisinObrit Как бы вы описали, что это new B(1,2,3)такое?
user2030677
1
@ user2030677: новое выражение с использованием ключевого слова new, имени типа и списка аргументов конструктора. Это все еще не «прямой вызов конструктора».
Гонки за легкостью на орбите,
«Clang неверен, поскольку он даже не вызывает правильную функцию.»: Я думаю (из-за замечания OP о скобках в объявлениях), что Clang интерпретирует C::C (y); как C::C y;, то есть определение переменной y типа C (с использованием введенного типа C: : C при ошибочном игнорировании все более безумной спецификации языка 3.4.1,2, которая делает C :: C конструктором). Это не такая уж явная ошибка, как вы думаете, imo.
Питер - Восстановить Монику