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


158

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

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;, но мне любопытно узнать, что ()делает extra в компиляторе в первом примере, который затем не работает при его повторном применении в второй пример. Является ли A a((B()));обходной путь конкретным исключением, записанным в стандарте?


20
(B())это просто выражение C ++, не более того. Это не исключение. Единственное отличие, которое он имеет, состоит в том, что его невозможно проанализировать как тип, и это не так.
Павел Минаев

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

11
Отличное замечание litb - это тонкий, но важный момент, на который стоит обратить внимание - причина неоднозначности в этом объявлении 'A a (B ())' заключается в разборе 'B ()' -> это может быть как выражение & объявление & компилятор должен 'выбрать' decl вместо expr - поэтому, если B () является decl, тогда 'a' может быть только func decl (не переменная decl). Если бы '()' было разрешено быть инициализатором, 'A a ()' было бы неоднозначным - но не expr vs decl, а var decl vs func decl - не существует правила, чтобы отдавать предпочтение одному decl другому - и так далее () здесь просто не допускается в качестве инициализатора - и двусмысленность не возрастает.
Фейсал Вали

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

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

Ответы:


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

Если бы одна из этих функций не существовала или имела другой синтаксис (как {}в 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 для объявлений функций всегда будет применяться.


12

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

A a(());

использование

A a=A();

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

6

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

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

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

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.