Странный код, который компилируется с g++

Следующий код успешно компилируется с g++ 4.8.1:

int main()
{
    int(*)();
}

Это похоже на простое объявление указателя на функцию:

int(*f)();

Он не компилируется с clang 3.4 и vС++ 2013.

Является ли это ошибкой компилятора или одним из темных мест стандарта?


Список похожих странных фрагментов кода, которые компилируются с помощью g++ 4.8.1 (обновлено):

  • int(*)();

  • int(*);

  • int(*){};

  • int(*());

Живой пример с этими странными фрагментами кода.

Обновление 1: @Ali добавило интересную информацию в комментарии:

Все 4 случая дают ошибку компиляции с соединительной шиной 3.5 (202594) и компилируются с помощью gcc 4.9 trunk (20140302). Поведение одинаково с -std=c++98 -pedantic, за исключением int(*){};, что понятно; расширенные списки инициализаторов доступны только с -std=c++11.

Обновить 2: Как @CantChooseUsernames, отмеченный в его answer они по-прежнему компилируются даже при инициализации, и для них не создается сборка g++ (ни с инициализацией, ни без нее) даже без какой-либо активированной оптимизации:

  • int(*)() = 0;

  • int(*) = 0;

  • int(*){} = 0;

  • int(*()) = 0;

Живой пример с инициализацией.

Обновление 3: Я был очень удивлен, обнаружив, что int(*)() = "Hello, world!"; тоже компилируется (пока int(*p)() = "Hello, world!"; не компилируется, конечно).

Обновление 4: Это фантастика, но int(*){} = Hello, world!; компилируется отлично. И следующий чрезвычайно странный фрагмент кода: int(*){}() = -+*/%&|^~.,:!?$()[]{}; (живой пример).

Обновить 5:. @zwol отмечен в его комментарии

Это и ряд связанных синтаксических проблем отслеживаются как gcc ошибка 68265.

Ответ 1

В соответствии со стандартом С++ (стр. 6 раздела 7 Декларации)

6 Каждый init-declarator в списке init-declarator содержит точно один идентификатор-id, который является именем, объявленным этим init-declarator и, следовательно, одно из имен, объявленных объявлением

Так что это просто ошибка компилятора.

Действительный код может выглядеть как, например, (кроме объявления указателя функции, показанного вами), хотя я не могу его скомпилировать с помощью моего MS VС++ 2010.

int(*p){};

Кажется, что компилятор, который вы используете для тестирования, допускает объявления без идентификатора-декларатора.

Также учтите следующий параграф раздела 8.1. Названия типов

1 Чтобы явно указать преобразования типов и как аргумент sizeof, alignof, new или typeid, имя типа должно быть указано. Это можно сделать с помощью идентификатора типа, который является синтаксически объявление для переменной или функции этого типа, которая опускает имя объекта.

Ответ 2

Я не уверен, насколько это помогает, но я попробовал следующее (clang 3.3, g++ 4.8.1):

using P = int(*)();
using Q = int*;
P; // warning only
Q; // warning only
int(*)(); // error (but only in clang)
int*;     // error
int(*p)(); // ok
int *q;    // ok

С другой стороны, все компилируется в g++ 4.8.2 и 4.9.0. К сожалению, у меня нет clang 3.4.

Очень грубо, объявление [iso section 7] состоит из следующих частей в порядке:

  • необязательные спецификаторы префикса (например, static, virtual)
  • базовый тип (например, const double, vector<int>)
  • (например, n, *p, a[7], f(int))
  • необязательные спецификаторы функции суффикса (например, const, noexcept)
  • необязательный инициализатор или тело функции (например, = {1,2,3} или { return 0; }

Теперь декларатор грубо состоит из имени и необязательно некоторых операторов-деклараторов [iso 8/4].

Операторы префикса, например:

  • * (указатель)
  • *const (постоянный указатель)
  • & (ссылка lvalue)
  • && (ссылка rvalue)
  • auto (возвращаемый тип функции, при завершении)

Операторы Postfix, например:

  • [] (массив)
  • () (функция)
  • -> (возвращаемый тип возвращаемого значения)

Вышеуказанные операторы были разработаны для отражения их использования в выражениях. Операторы Postfix привязываются более жестко, чем префикс, а круглые скобки могут использоваться для изменения их порядка: int *f() - это функция, возвращающая указатель на int, тогда как int (*f)() является указателем на функцию, возвращающую int.

Возможно, я ошибаюсь, но я думаю, что эти операторы не могут быть в объявлении без имени. Поэтому, когда мы пишем int *q;, тогда int является базовым типом, а *q является декларатором, состоящим из префиксного оператора *, за которым следует имя q. Но int *; не может отображаться сам по себе.

С другой стороны, когда мы определяем using Q = int*;, объявление Q; отлично само по себе, потому что q является базовым типом. Конечно, поскольку мы ничего не объявляем, мы можем получить сообщение об ошибке или предупреждение в зависимости от параметров компилятора, но это другая ошибка.

Выше всего лишь мое понимание. Что означает стандарт (например, N3337), это [iso 8.3/1]:

Каждый декларатор содержит ровно один идентификатор объявления; он называет объявленный идентификатор. Неквалифицированный идентификатор, встречающийся в идентификаторе-деклараторе, должен быть простым идентификатором, за исключением объявления некоторых специальных функций (12.3 [пользовательские преобразования], 12.4 [деструкторы], 13.5 [перегруженные операторы]) и для объявления специализированных шаблонов или частичные специализации (14.7).

(примечания в квадратных скобках мои). Поэтому я понимаю, что int(*)(); должен быть недействительным, и я не могу сказать, почему он имеет другое поведение в clang и разных версиях g++.

Ответ 3

Вы можете использовать это: http://gcc.godbolt.org/, чтобы просмотреть сборку.

int main()
{
    int(*)() = 0;
    return 0;
}

Формирует:

main:
    pushq   %rbp
    movq    %rsp, %rbp
    movl    $0, %eax
    popq    %rbp
    ret

Что эквивалентно: int main() {return 0;} Таким образом, даже при отсутствии оптимизации gcc просто не создает сборку для нее. Должен ли он давать предупреждение или ошибку? У меня нет подсказки, но для неназванного указателя func оно не заботится и ничего не делает.

Однако:

int main()
{
    int (*p)() = 0;
    return 0;
}

Без оптимизации будет генерироваться:

main:
    pushq   %rbp
    movq    %rsp, %rbp
    movq    $0, -8(%rbp)
    movl    $0, %eax
    popq    %rbp
    ret

который выделяет 8 байтов в стеке.