Програма складається по-різному у трьох основних компіляторах 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.