Програма складається по-різному у трьох основних компіляторах 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. Він успішно компілюється із затискачем 3.5 та відбитками C

Тож обов’язкове запитання, яке з них правильне? :)

(Я сильно хитнувся до версії кланг, хоча і msvc спосіб зупинити оголошення змінної після просто зміни типу технічно його typedef здається дивним)


3
C::C y;не має сенсу, правда? Також НЕ C::C (y); Спочатку я подумав , що це екземпляр Most-прикрий-Аналізувати stackoverflow.com/questions/tagged/most-vexing-parse , але тепер я думаю , що це просто невизначений поведінка означає все три компіляторів «правильно.»
Дейл Вілсон

4
Клац №3, безумовно, помиляється, # 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якості параметра конструктора; і Кланг неправильно трактує це як оголошення змінної, що називається yтипу C.


2
Так, 3.4.3.1/2 є ключовим. Хороша робота!
Гонки легкості на орбіті

Він говорить "У пошуку, в якому імена функцій не ігноруються". Мені здається, що в наведених прикладах, зокрема A::A a;, імена функцій слід ігнорувати - чи ні?
Коламбо

1
Переходячи до нумерації в N4296, ключ дійсно є 3.4.3.1/2.1: "якщо ім'я, вказане після специфікованого вкладеного імені, під час пошуку в C є введеним класом-ім'ям C [...] замість цього імені вважається ім'ям конструктора класу C. " Підсумок Майка трохи спрощений - наприклад, typedef імені класу всередині класу дозволив би вкладеному іменові вкладеного імені, відмінному від імені класу, все ще посилатися на ім'я класу, тому воно все одно посилатиметься на ctor.
Джеррі Труну

2
@Mgetz: З питання: "Він успішно збирається з MSVC2013 / 2015 та друкує C (B *)" .
Гонки легкості на орбіті

2
Для повноти це повинно з’ясувати, чи вона непрацездатна з необхідною діагностикою, або неправильно сформована, не вимагаючи діагностики. Якщо остання, то всі компілятори "праві".
ММ

16

G ++ є правильним, оскільки дає помилку. Тому що конструктор не міг викликати безпосередньо в такому форматі без newоператора. І хоча ваш код дзвонить C::C, він виглядає як виклик конструктора. Однак, відповідно до стандарту 3.4.3.1 C ++ 11, це не юридичний виклик функції або ім'я типу ( див. Відповідь Майка Сеймура ).

Кланг помиляється, оскільки навіть не викликає правильну функцію.

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)таке?
користувач2030677

1
@ user2030677: новий вираз із використанням ключового слова new, імені типу та списку аргументів конструктора. Це все ще не "прямий виклик конструктора".
Гонки легкості на орбіті

"Clang помиляється, оскільки він навіть не викликає правильну функцію.": Я думаю (оскільки зауваження ОП про дужки в деклараціях), що Clang трактує C::C (y); як C::C y;, тобто визначення змінної y типу C (використовуючи введений тип C: : C, помилково ігноруючи все більш божевільну специфікацію мови 3.4.1,2, що робить C :: C конструктором). Це не зовсім очевидна помилка, як вам здається, іммо.
Пітер - Відновити Моніку
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.