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

#c #string

#c

Вопрос:

Среди многих вещей, которым научило меня переполнение стека, есть так называемый «самый неприятный синтаксический анализ», который классически демонстрируется с помощью такой строки, как

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

Хотя для большинства интуитивно кажется, что это объявление объекта a типа A , принимающего временный B объект в качестве параметра конструктора, на самом деле это объявление функции a , возвращающей an A , принимая указатель на функцию, которая возвращает B и сама не принимает параметров. Аналогично линия

 A a(); //declares a function
 

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

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

Однако во втором случае выполнение того же самого приводит к ошибке компиляции

 A a(()); //compile error
 

Мой вопрос в том, почему? Да, я очень хорошо знаю, что правильный «обходной путь» — это изменить его на A a; , но мне любопытно узнать, что extra () делает для компилятора в первом примере, который затем не работает при повторном применении его во втором примере. Является A a((B())); ли обходное решение конкретным исключением, записанным в стандарт?

Комментарии:

1. (B()) это просто выражение C , не более того. Это не какое-то исключение. Единственное отличие, которое это имеет, заключается в том, что его невозможно проанализировать как тип, и поэтому это не так.

2. Следует также отметить, что второй случай A a(); не относится к той же категории. Для компилятора никогда не существует другого способа его анализа: инициализатор в этом месте никогда не состоит из пустых круглых скобок, так что это всегда объявление функции.

3. отличный момент litb является тонким, но важным, и его стоит подчеркнуть — причина, по которой двусмысленность существует в этом объявлении ‘A a(B ())’, заключается в синтаксическом анализе ‘B()’ -> это может быть как выражение, так и объявление, и компилятор должен «выбрать»decl вместо expr — так что, если B() является decl, то ‘a’ может быть только функцией decl (не переменной decl). Если бы ‘()’ было разрешено быть инициализатором, ‘A a()’ был бы неоднозначным — но не expr против decl, а var decl против func decl — нет правила предпочесть один decl другому — и поэтому ‘()’ здесь просто не допускается в качестве инициализатора -и двусмысленность не возникает.

4. A a(); это не пример самого неприятного разбора . Это просто объявление функции, как и в C.

5. «правильный «обходной путь» — изменить его на A a; » неверно. Это не даст вам инициализации типа POD. Чтобы получить инициализацию , напишите A a{}; .

Ответ №1:

Просвещенного ответа нет, это просто потому, что язык C не определяет его как допустимый синтаксис… Так оно и есть, по определению языка.

Если у вас действительно есть выражение внутри, то оно является действительным. Например:

  ((0));//compiles
 

Еще проще говоря: because (x) является допустимым выражением C , в то время () как это не так.

Чтобы узнать больше о том, как определяются языки и как работают компиляторы, вам следует изучить формальную теорию языка или, более конкретно, контекстно-свободные грамматики (CFG) и связанные с ними материалы, такие как конечные автоматы. Если вы заинтересованы в этом, хотя страниц Википедии будет недостаточно, вам придется обзавестись книгой.

Комментарии:

1. Еще проще говоря: because (x) является допустимым выражением C , в то время () как это не так.

2. Я принял этот ответ, кроме того, комментарий Павла к моему первоначальному вопросу мне очень помог

Ответ №2:

Окончательное решение этой проблемы состоит в том, чтобы перейти к унифицированному синтаксису инициализации C 11, если вы можете.

 A a{};
 

http://www.stroustrup.com/C 11FAQ.html#uniform-инициализация

Ответ №3:

Деклараторы функций 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: %dn", 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 . Грамматика объявлений пытается сопоставить имя как функцию, прежде чем вернуться к новому синтаксису с () , если это не удается.

If one of those features wouldn’t exist, or had a different syntax (like {} in C 11), this issue would never have happened for syntax with one argument.

Now you may ask why A a((B())) works. Well, let’s declare output_result with useless parentheses.

 int output_result((int()));
 

It won’t work. The grammar requires the variable to not be in parentheses.

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

However, C expects standard expression here. In C , you can write the following code.

 int value = int();
 

And the following code.

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

C expects expression inside inside parentheses to be… well… expression, as opposed to the type C expects. Parentheses don’t mean anything here. However, by inserting useless parentheses, the C function declaration is not matched, and the new syntax can be matched properly (which simply expects an expression, such as 2 2 ).

More arguments in constructor

Surely one argument is nice, but what about two? It’s not that constructors may have just one argument. One of built-in classes which takes two arguments is std::string

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

This is all well and fine (technically, it would have most vexing parse if it would be written as std::string wat(int(), char()) , but let’s be honest — who would write that? But let’s assume this code has a vexing problem. You would assume that you have to put everything in parentheses.

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

Not quite so.

 <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 _Allocamp;) [with _CharT = char; _Traits = std::char_traits<char>; _Alloc = std::allocator<char>]’ [-fpermissive]
     basic_string<_CharT, _Traits, _Alloc>::
     ^
 

Я не уверен, почему g пытается преобразовать char в const char * . В любом случае, конструктор был вызван только с одним значением типа char . Нет перегрузки, которая имеет один аргумент типа char , поэтому компилятор сбит с толку. Вы можете спросить — почему аргумент имеет тип char?

 (100, '.')
 

Да, , вот оператор запятой. Оператор запятой принимает два аргумента и выдает аргумент с правой стороны. На самом деле это не очень полезно, но это то, что нужно знать для моего объяснения.

Вместо этого, чтобы решить самую неприятную задачу синтаксического анализа, необходим следующий код.

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

Аргументы заключены в круглые скобки, а не все выражение целиком. На самом деле, только одно из выражений должно быть в круглых скобках, так как достаточно немного отступить от грамматики C, чтобы использовать функцию C . Ситуация подводит нас к точке нулевых аргументов.

Нулевые аргументы в конструкторе

Возможно, вы заметили eighty_four функцию в моем объяснении.

 int eighty_four();
 

Да, на это также влияет самый неприятный синтаксический анализ. Это допустимое определение, и вы, скорее всего, видели его, если создавали заголовочные файлы (а вам следовало бы). Добавление круглых скобок этого не исправит.

 int eighty_four(());
 

Почему это так? Ну, () это не выражение. В C вы должны поместить выражение в круглые скобки. Вы не можете писать auto value = () на C , потому () что это ничего не значит (и даже если бы это было так, как пустой кортеж (см. Python ), это был бы один аргумент, а не ноль). Практически это означает, что вы не можете использовать сокращенный синтаксис без использования синтаксиса C 11 {} , поскольку в нем нет выражений, которые можно заключить в круглые скобки, а грамматика C для объявлений функций всегда будет применяться.

Ответ №4:

Вы могли бы вместо этого

 A a(());
 

используйте

 A a=A();
 

Комментарии:

1. «Лучший обходной путь» не эквивалентен. int a = int(); инициализируется a с 0, int a; остается a неинициализированным. Правильный обходной путь — использовать A a = {}; для агрегатов, A a; когда инициализация по умолчанию делает то, что вы хотите, и A a = A(); во всех других случаях — или просто использовать A a = A(); последовательно. В C 11 просто используйте A a {};

Ответ №5:

Самыми внутренними скобками в вашем примере будет выражение, а в C грамматика определяет an expression как an assignment-expression или another expression , за которым следует запятая и другое assignment-expression (Приложение A.4 — Краткое изложение грамматики / выражения).

Грамматика далее определяет an assignment-expression как один из нескольких других типов выражений, ни одно из которых не может быть ничем (или только пробелом).

Итак, причина, по которой вы не можете A a(()) этого сделать, заключается просто в том, что грамматика этого не позволяет. Однако я не могу ответить, почему люди, создавшие C , не разрешили это конкретное использование пустых скобок в качестве своего рода особого случая — я бы предположил, что они предпочли бы не вводить такой особый случай, если бы была разумная альтернатива.