Моя попытка инициализации значения интерпретируется как объявление функции, и почему не A a(()); реши это?
Среди многих вещей, которым научил меня переполнение стека, является то, что известно как "самый неприятный синтаксический анализ", что классически демонстрируется такой строкой, как
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()));
обойти конкретное исключение, записанное в стандарте?
5 ответов
Просветленного ответа нет, просто потому, что он не определен как допустимый синтаксис языком C++... Так оно и есть, по определению языка.
Если у вас есть выражение внутри, то оно действительно. Например:
((0));//compiles
Еще проще говоря: потому что (x)
является допустимым выражением C++, в то время как ()
не является.
Чтобы узнать больше о том, как определяются языки и как работают компиляторы, вам следует изучить теорию формальных языков или, в частности, контекстно-свободные грамматики (CFG) и связанные с ними материалы, такие как конечные автоматы. Если вам это интересно, хотя страниц википедии будет недостаточно, вам нужно будет получить книгу.
Окончательное решение этой проблемы - перейти к унифицированному синтаксису инициализации C+11, если вы можете.
A a{};
Описатели функции 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++. Грамматика объявлений пытается сопоставить имя как функцию, прежде чем вернуться к новому синтаксису с ()
если не получится.
Если одна из этих функций не существует или имеет другой синтаксис (например, {}
в 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), ('.'));
Аргументы указаны в скобках, а не во всем выражении. Фактически, только одно из выражений должно быть в скобках, так как достаточно немного отойти от грамматики C, чтобы использовать функцию C++. Вещи подводят нас к нулю аргументов.
Нулевые аргументы в конструкторе
Вы могли заметить eighty_four
Функция в моем объяснении.
int eighty_four();
Да, это также затронуто самым неприятным анализом. Это правильное определение, которое вы, скорее всего, видели, если создавали заголовочные файлы (и вам следует). Добавление скобок не исправляет это.
int eighty_four(());
Почему это так? Что ж, ()
это не выражение. В C++ вы должны поместить выражение в скобки. Вы не можете написать auto value = ()
в C++, потому что ()
ничего не значит (и даже если бы сделал, как пустой кортеж (см. Python), это был бы один аргумент, а не ноль). Практически это означает, что вы не можете использовать сокращенный синтаксис без использования C++ 11 {}
синтаксис, поскольку в скобках нет выражений, а грамматика C для объявлений функций всегда будет применяться.
Самым внутренним паренсом в вашем примере будет выражение, а в C++ грамматика определяет expression
быть assignment-expression
или другой expression
с запятой и другим assignment-expression
(Приложение А.4 - Грамматическое резюме / Выражения).
Грамматика дополнительно определяет assignment-expression
как один из нескольких других типов выражения, ни один из которых не может быть ничем (или только пробелом).
Так что причина, по которой вы не можете иметь A a(())
просто потому, что грамматика не позволяет этого. Тем не менее, я не могу ответить, почему люди, создавшие C++, не допускали такого особого использования пустых паренов в качестве какого-то особого случая - я предполагаю, что они бы не стали помещать такой особый случай, если бы был разумная альтернатива.