Самый неприятный синтаксический анализ: почему A a (()); Работа?
Среди многих вещей, которые Qaru научил меня, это то, что известно как "самый неприятный синтаксический анализ", который классически демонстрируется с помощью строки, такой как
A a(B()); //declares a function
В то время как это для большинства интуитивно представляется объявлением объекта a
типа a
, взяв временный объект B
в качестве параметра конструктора, это фактически объявление функции a
, возвращающее a a
, взяв указатель на функцию, которая возвращает B
и сама не принимает никаких параметров. Аналогично, линия
A a(); //declares a function
также относится к той же категории, поскольку вместо объекта он объявляет функцию. Теперь в первом случае обычным обходным решением этой проблемы является добавление дополнительного набора скобок/скобок вокруг B()
, поскольку компилятор затем интерпретирует его как объявление объекта
A a((B())); //declares an object
Однако во втором случае выполнение этого же приводит к ошибке компиляции
A a(()); //compile error
Мой вопрос: почему? Да, я очень хорошо знаю, что правильное "обходное решение" заключается в том, чтобы изменить его на A a;
, но мне любопытно узнать, что это такое, что дополнительный ()
для компилятора в первом примере, который затем не делает " t при повторном использовании его во втором примере. Обходным путем A a((B()));
является конкретное исключение, записанное в стандарте?
Ответы
Ответ 1
Нет просветленного ответа, это просто потому, что он не определен как допустимый синтаксис языком C++... Так что это так, по определению языка.
Если у вас есть выражение внутри, то оно действительно. Например:
((0));//compiles
Еще проще: потому что (x)
является допустимым C++ выражением, а ()
- нет.
Чтобы узнать больше о том, как определяются языки и как работают компиляторы, вы должны изучить теорию формального языка или, более конкретно, контекстные бесплатные грамматики (CFG) и связанные с ними материалы, такие как конечные автоматы. Если вас это интересует, хотя страниц в википедии недостаточно, вам нужно будет получить книгу.
Ответ 2
Окончательным решением этой проблемы является переход на синтаксис синтаксиса C + 11, если вы можете.
A a{};
http://www.stroustrup.com/C++11FAQ.html#uniform-init
Ответ 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: %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 имеют приоритет над новыми функциями С++. Грамматика деклараций пытается сопоставить имя как функцию, прежде чем возвращаться к новому синтаксису с помощью ()
, если он терпит неудачу.
Если одна из этих функций не существовала или имела другой синтаксис (например, {}
в С++ 11), эта проблема никогда бы не произошла для синтаксиса с одним аргументом.
Теперь вы можете спросить, почему работает A a((B()))
. Ну, пусть объявляет output_result
бесполезными круглыми скобками.
int output_result((int()));
Это не сработает. Грамматика требует, чтобы переменная не была в круглых скобках.
<stdin>:1:19: error: expected declaration specifiers or ‘...’ before ‘(’ token
Однако С++ ожидает стандартное выражение здесь. В С++ вы можете написать следующий код.
int value = int();
И следующий код.
int value = ((((int()))));
С++ ожидает, что выражение внутри внутри круглых скобок будет... well... expression, в отличие от типа 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>::
^
Я не уверен, почему g++ пытается преобразовать char
в const char *
. В любом случае конструктор вызывался только одним значением типа char
. Нет перегрузки, которая имеет один аргумент типа char
, поэтому компилятор запутан. Вы можете спросить - почему аргумент имеет тип char?
(100, '.')
Да, ,
здесь есть запятый оператор. Оператор запятой принимает два аргумента и дает аргумент правой стороны. Это не очень полезно, но это должно быть известно для моего объяснения.
Вместо этого, чтобы решить самый неприятный синтаксический анализ, необходим следующий код.
std::string hundred_dots((100), ('.'));
Аргументы находятся в круглых скобках, а не во всем выражении. Фактически, только одно из выражений должно быть в круглых скобках, так как достаточно немного сломать C-грамматику, чтобы использовать функцию С++. Вещи приводят нас к нулю аргументов.
Нулевые аргументы в конструкторе
Возможно, вы заметили функцию eighty_four
в моем объяснении.
int eighty_four();
Да, на это также влияет самый неприятный синтаксический разбор. Это правильное определение, которое вы, скорее всего, видели, если создали файлы заголовков (и вы должны). Добавление скобок не исправляет.
int eighty_four(());
Почему это так? Ну, ()
не является выражением. В С++ вы должны поставить выражение между круглыми скобками. Вы не можете написать auto value = ()
в С++, потому что ()
ничего не значит (и даже если бы это было так, как пустой кортеж (см. Python), это был бы один аргумент, а не ноль). Практически это означает, что вы не можете использовать сокращенный синтаксис без использования синтаксиса С++ 11 {}
, так как в круглых скобках нет выражений, а C-грамматика для деклараций функций всегда будет применяться.
Ответ 4
Вместо этого вы можете
A a(());
использование
A a=A();
Ответ 5
Самые внутренние парны в вашем примере будут выражением, а в С++ грамматика определяет expression
как assignment-expression
или другой expression
, за которым следует запятая, а другая assignment-expression
(Приложение A.4 - Грамматика/Выражения).
Далее грамматика определяет assignment-expression
как один из нескольких других типов выражений, ни один из которых не может быть ничем (или только пропуском).
Таким образом, причина, по которой вы не можете иметь A a(())
, просто потому, что грамматика не позволяет этого. Тем не менее, я не могу ответить, почему люди, которые создали С++, не позволяли этому конкретному использованию пустых парнеров как своего рода особый случай - я бы предположил, что они предпочли бы не вставлять такой особый случай, если бы разумная альтернатива.