Моя спроба ініціалізації значення інтерпретується як декларація функції, і чому це не A a (()); виріши це?


158

Серед багатьох речей, якими мене навчив Stack Overflow, - це те, що відомо як "найбільш роздратований синтаксичний аналіз", який класично демонструється такою лінією, як

A a(B()); //declares a function

Хоча для більшості це інтуїтивно виглядає оголошенням об'єкта aтипу A, приймаючи тимчасовий Bоб'єкт як параметр конструктора, це фактично оголошення функції, що aповертається A,, вказівник на функцію, яка повертається Bі сама не приймає ніяких параметрів . Аналогічно лінії

A a(); //declares a function

також підпадає під цю ж категорію, оскільки замість об'єкта він оголошує функцію. Тепер, у першому випадку, звичайним рішенням цього питання є додавання додаткового набору дужок / дужок навколо B(), оскільки компілятор буде інтерпретувати його як декларацію об'єкта

A a((B())); //declares an object

Однак у другому випадку те саме призводить до помилки компіляції

A a(()); //compile error

Моє запитання: чому? Так, я дуже добре усвідомлюю, що правильний "спосіб вирішення" - це змінити його A a;, але мені цікаво дізнатися, що це таке додаткове ()для компілятора в першому прикладі, який потім не працює при повторному застосуванні його в другий приклад. Чи A a((B()));вхідний стандарт вписаний у стандарт?


20
(B())це лише вираз C ++, не більше того. Це не будь-який виняток. Єдина відмінність, яку він робить - це те, що немає можливості його розібрати як тип, і так це не так.
Павло Мінаєв

12
Слід також зазначити, що другий випадок, неA a(); відноситься до тієї ж категорії. Для компілятора ніколи не існує різного способу його розбору: ініціалізатор у цьому місці ніколи не складається з порожніх дужок, тому це завжди оголошення функції.
Йоханнес Шауб - ліб

11
Відмінна точка litb є тонкою, але важливою і її варто підкреслити - причина неоднозначності існує в цій декларації "A a (B ())" в синтаксичному аналізі "B ()" -> це може бути і виразом & декларація & компілятор повинен "вибрати" decl над expr - так що, якщо B () є decl, "a" може бути лише функцією decl (не змінною decl). Якщо дозволено "()" бути ініціалізатором, "A a ()" буде неоднозначним, але не expr vs decl, а var decl vs funkc decl - немає правила віддавати перевагу одному decl над іншим - і так "() 'тут просто не дозволено як ініціалізатор - і неоднозначність не зростає.
Faisal Vali

6
A a();це НЕ є приклад найбільш неприємного розбору . Це просто оголошення функції, як і в C.
Піт Бекер

2
"правильне" вирішення "- це змінити його на A a;" невірно. Це не дасть вам ініціалізації типу POD. Для отримання спонукань пишіть A a{};.
Ура та хт. - Альф

Відповіді:


70

Немає просвітленої відповіді, це просто тому, що вона не визначена як допустимий синтаксис мовою C ++ ... Так це і є, за визначенням мови.

Якщо ви маєте вираз всередині, воно дійсне. Наприклад:

 ((0));//compiles

Ще простіше кажучи: тому що (x)це дійсний вираз C ++, поки ()це не так.

Щоб дізнатися більше про те, як визначаються мови та як працюють компілятори, ви повинні дізнатися про теорію формальної мови або конкретніше вільних граматик контексту (CFG) та пов'язані з ними матеріали, такі як машини з кінцевим станом. Якщо ви зацікавлені в тому, що хоч сторінок вікіпедій буде недостатньо, вам доведеться отримати книгу.


45
Ще простіше кажучи: тому що (x)це дійсний вираз C ++, поки ()це не так.
Павло Мінаєв

Я прийняв цю відповідь, окрім того, коментар Павла до мого початкового запитання мені дуже допоміг
GRB


29

Декларатори функцій C

Перш за все, існує C. В C - A a()це оголошення функції. Наприклад, putcharмає таку декларацію. Зазвичай такі декларації зберігаються у файлах заголовків, однак нічого не заважає писати їх вручну, якщо ви знаєте, як виглядає декларація функції. Імена аргументів необов’язково в деклараціях, тому я опустив це в цьому прикладі.

int putchar(int);

Це дозволяє написати такий код.

int puts(const char *);
int main() {
    puts("Hello, world!");
}

C також дозволяє визначати функції, які приймають функції як аргументи, з приємним читабельним синтаксисом, схожим на виклик функції (ну, це читається, доки ви не повернете покажчик на функцію).

#include <stdio.h>

int eighty_four() {
    return 84;
}

int output_result(int callback()) {
    printf("Returned: %d\n", callback());
    return 0;
}

int main() {
    return output_result(eighty_four);
}

Як я вже згадував, C дозволяє опускати імена аргументів у файлах заголовків, тому output_resultв файлі заголовка це виглядатиме так.

int output_result(int());

Один аргумент у конструкторі

Ви цього не впізнаєте? Ну, нагадаю.

A a(B());

Так, це саме те саме оголошення функції. Aє int, aє output_resultі Bє int.

Ви можете легко помітити конфлікт C за допомогою нових функцій C ++. Якщо бути точним, конструктори мають назву класу та круглі дужки та чергують синтаксис декларації з ()замість =. За задумом C ++ намагається бути сумісним із кодом С, а тому йому доводиться мати справу з цією справою - навіть якщо практично нікого не цікавить. Тому старі функції C мають пріоритет перед новими функціями C ++. Граматика декларацій намагається відповідати імені як функції, перед тим як повернутися до нового синтаксису, ()якщо воно не вдалося .

Якщо одна з цих функцій не існувала б або мала інший синтаксис (як, наприклад, {}у C ++ 11), ця проблема ніколи не сталася б для синтаксису з одним аргументом.

Тепер ви можете запитати, чому A a((B()))працює. Що ж, давайте оголосимо output_resultнепотрібними дужками.

int output_result((int()));

Це не спрацює. Граматика вимагає, щоб змінна не була в дужках.

<stdin>:1:19: error: expected declaration specifiers or ‘...’ before ‘(’ token

Однак C ++ очікує тут стандартного вираження. У C ++ ви можете написати наступний код.

int value = int();

І наступний код.

int value = ((((int()))));

C ++ очікує, що вираз усередині дужок буде ... добре ... вираз, на відміну від типу C, який очікує. Дужки тут нічого не означають. Однак, вставляючи непотрібні дужки, декларація функції C не узгоджується, і новий синтаксис можна відповідати належним чином (що просто очікує виразу, наприклад 2 + 2).

Більше аргументів у конструкторі

Безумовно, один аргумент приємний, але як бути два? Справа не в тому, що у конструкторів може бути лише один аргумент. Один із вбудованих класів, який бере два аргументи, цеstd::string

std::string hundred_dots(100, '.');

Це все добре і добре (технічно, це було б найбільш роздратованим синтаксичним аналізом, якби це було написано так std::string wat(int(), char()), але давайте будемо чесними - хто це написав би? Але припустимо, у цього коду є неприємна проблема. Ви б припустили, що вам доведеться поставити все в дужках.

std::string hundred_dots((100, '.'));

Не зовсім так.

<stdin>:2:36: error: invalid conversion from char to const char*’ [-fpermissive]
In file included from /usr/include/c++/4.8/string:53:0,
                 from <stdin>:1:
/usr/include/c++/4.8/bits/basic_string.tcc:212:5: error:   initializing argument 1 of std::basic_string<_CharT, _Traits, _Alloc>::basic_string(const _CharT*, const _Alloc&) [with _CharT = char; _Traits = std::char_traits<char>; _Alloc = std::allocator<char>]’ [-fpermissive]
     basic_string<_CharT, _Traits, _Alloc>::
     ^

Я не знаю , чому г намагається ++ перетворити charв const char *. Так чи інакше, конструктор викликався лише одним значенням типу char. Немає перевантаження, яка має один аргумент типу char, тому компілятор плутається. Ви можете запитати - чому аргумент має тип char?

(100, '.')

Так, ,ось оператор з комами. Оператор з комами приймає два аргументи та надає правий аргумент. Це насправді не корисно, але моє пояснення має бути відомим.

Натомість, щоб вирішити найбільш неприємний синтаксис, потрібен наступний код.

std::string hundred_dots((100), ('.'));

Аргументи є в дужках, а не в усьому виразі. Насправді, лише один із виразів повинен бути в дужках, оскільки для використання функції C ++ достатньо трохи відірватися від граматики С. Речі доводять нас до точки нульових аргументів.

Нульові аргументи в конструкторі

Можливо, ви помітили цю eighty_fourфункцію в моєму поясненні.

int eighty_four();

Так, на це впливає і найприємніший розбір. Це правильне визначення, і це ви, швидше за все, бачили, якщо ви створювали файли заголовків (і вам слід). Додавання дужок це не виправить.

int eighty_four(());

Чому це так? Ну, ()це не вираз. У C ++ ви повинні ставити вираз між дужками. Ви не можете писати auto value = ()на C ++, тому що ()нічого не означає (і навіть якби це було, як порожній кортеж (див. Python), це був би один аргумент, а не нуль). Це практично означає, що ви не можете використовувати скорочений синтаксис без використання синтаксису C ++ 11 {}, оскільки в дужках немає виразів, а граматика C для декларацій функцій завжди застосовуватиметься.


12

Ви можете замість цього

A a(());

використання

A a=A();

32
"Кращий спосіб" не є рівнозначним. int a = int();ініціалізується aз 0, int a;залишає aнеініціалізованим. Правильним рішенням є використання A a = {};для агрегатів, A a;коли ініціалізація за замовчуванням виконує те, що ви хочете, і A a = A();у всіх інших випадках - або просто використовує A a = A();послідовно. В C ++ 11 просто використовуйтеA a {};
Річард Сміт

6

Найпотаємніші парени у вашому прикладі будуть виразом, а в C ++ граматика визначає " expressiona" assignment-expressionабо "інше", expressionа потім кома та інше assignment-expression(додаток A.4 - "Граматичні підсумки / вирази").

Далі граматика визначає assignment-expressionяк один із кількох інших типів вираження, жодне з яких не може бути нічого (або лише пробілом).

Тож причина, яку ви не можете мати, A a(())- це просто тому, що граматика цього не дозволяє. Однак я не можу відповісти, чому люди, які створили C ++, не дозволили використовувати саме це порожніми паролями як якийсь особливий випадок - я б припустив, що вони краще не ставлять у такий особливий випадок, якби розумна альтернатива.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.