Среди многих вещей, которые 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;, но мне любопытно узнать, что это такое, что дополнительный () делает для компилятора в первом примере, t при повторном использовании его во втором примере. Обходное решение A a((B())); содержит конкретное исключение, записанное в стандарт?
Прежде всего, существует 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 ++. Грамматика деклараций пытается сопоставить имя как функцию, прежде чем возвращаться к новому синтаксису с помощью (), если он не работает.
Если одна из этих функций не существовала или имела другой синтаксис (например, {} в 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>::
^
Я не уверен, почему g ++ пытается преобразовать char в const char *. В любом случае конструктор вызывался только с одним значением типа char. Нет перегрузки, которая имеет один аргумент типа char, поэтому компилятор запутан. Вы можете спросить - почему аргумент имеет тип char?
(100, '.')
Да, , здесь является оператором запятой. Оператор запятой принимает два аргумента, и дает аргумент в правой части. Это не очень полезно, но это то, что должно быть известно моим объяснениям.
Вместо этого, чтобы решить самый неприятный синтаксический разбор, необходим следующий код.
std::string hundred_dots((100), ('.'));
Аргументы находятся в круглых скобках, а не t все это выражение. Фактически, только одно из выражений должно быть в круглых скобках, так как достаточно немного сломать C-грамматику, чтобы использовать функцию C ++. Вещи приводят нас к нулю нулевых аргументов.
Возможно, вы заметили функцию eighty_four в моем объяснении.
int eighty_four();
Да, на это также влияет самый неприятный синтаксический разбор. Это правильное определение, и вы, скорее всего, видели, если создали файлы заголовков (и вам нужно). Добавление скобок не исправляет это.
int eighty_four(());
Почему это так? Ну, () не является выражением. В C ++ вы должны поставить выражение между круглыми скобками. Вы не можете писать auto value = () в C ++, потому что () ничего не значит (и даже если бы это было так, как пустой кортеж (см. Python), это был бы один аргумент, а не ноль). Практически это означает, что вы не можете использовать сокращенный синтаксис без использования синтаксиса {} C ++ 11, так как в скобках нет выражений, и C-грамматика для деклараций функций всегда будет применяться.
Окончательное решение этой проблемы заключается в том, чтобы перейти к синтаксису синтаксиса C + 11, если вы можете.
A a{};
http://www.stroustrup.com/C++11FAQ.html # равномерные иницы
Внутренние парсеры в вашем примере будут выражением, а в C ++ грамматика определяет expression как assignment-expression или другой expression, за которым следует запятая, а другая assignment-expression (Приложение A.4 - Грамматика / Выражения).
Грамматика далее определяет assignment-expression как один из нескольких других типов выражения, ни один из которых не может быть ничем (или только пропуском).
So причина, по которой вы не можете A a(()), просто потому, что грамматика не позволяет этого. Тем не менее, я не могу ответить, почему люди, которые создали C ++, не позволяли этому конкретному использованию пустых парнеров как своего рода особый случай - я бы предположил, что они предпочли бы не вставлять такой особый случай, если бы разумная альтернатива.
Вы могли бы вместо этого
A a(());
использовать
A a=A();