Почему вы не можете иметь несколько объявлений-спецификаторов-seq в декларации?

Ниже приведен неверный код:

int i = 0, double j = 2.0;

В проекте стандарта говорится, почему:

[N4140/7.1.6]

2 Как правило, не более одного типа-спецификатора допускается в полный decl-specifier-seq декларации или в type-specifier-seq или trailing-type-specifier-seq. Единственными исключениями из этого правила являются следующие:

- const можно комбинировать с любым спецификатором типа, кроме самого себя.

     

- volatile можно комбинировать с любым спецификатором типа, кроме самого себя.

     

- signed или unsigned можно комбинировать с char, long, short,   или int.

     

- short или long можно комбинировать с int.

     

- long можно комбинировать с double.

     

- long можно комбинировать с long.

Да, это предотвращает что-то глупое как int int, но я не вижу ничего плохого в неверном коде, опубликованном выше. Цитируя [N4140/7], простая декларация состоит из объявления decl-specifier-seq opt init-declarator-list opt;

[N4140/8] показывает, что список init-declarator состоит из init-declarator-list, init-declarator,

а init-declarator - инициализатор декларатора opt.

Поскольку мы имеем дело только с синтаксисом формы int i = 0, то мы должны заботиться о деклараторе ptr-declarator, который является noptr-декларатором, который является атрибутом-атрибутом declarator-id-seq opt, и, наконец, идентификатор declarator состоит только из ... opt id-expression.

Для полноты, [N4140/5.1.1] говорит, что id-выражение может быть неквалифицированным-id или просто идентификатором.

Если я пока не споткнулся, это то, что отражает грамматика.

int decl-specifier-seq

i unqualified-id

= 0 инициализатор

int i = 0 init-declarator

Так как простая декларация имеет spec-specifier-seq, только один spec-specifier-seq применяется ко всему списку init-declarator-list.

Как ни странно, это означает, что вы не можете сделать что-то вроде этого:

int i, const j;

Тем не менее:

int i, * j;

совершенно законно, потому что звезда является частью ptr-оператора. Но вы не можете этого сделать:

int i, const * j; // pointer to const int

Это означает, что в следующем коде i становится указателем на const int. Сюрприз!

int h = 25;
int const * j, * i = &h;
*i = 50; // error: assignment of read-only location '* i'

В [N4140/8] подразумевается намерение:

3 Каждый декларатор init в декларации анализируется отдельно как если он был в декларации сам по себе. 99

99) Декларация с несколькими деклараторами обычно эквивалентна соответствующая последовательность объявлений, каждая с одной описатель. Это

T D1, D2, ... Dn;

обычно эквивалентен

T D1; T D2; ... T Dn;

Вопрос в том, почему это так?


Если это было законно, вы могли бы сделать это для циклов, что несколько полезно.

Ответ 1

Короткий ответ: один оператор может разрешить только "одно объявление типа", но это объявление может объявлять "несколько идентификаторов". Кроме того, const/volatile являются либо классификаторами типов, либо указателями указателей, поэтому им нужен тип или указатель для привязки.

Длинный ответ:

Я никогда не читал стандарт, но здесь я иду...

"он предотвращает что-то глупое, как int int, но я не вижу ничего плохого в неверном коде, опубликованном выше."

  • Я вижу, что один оператор может разрешить вам только одно объявление, но одно объявление может позволить вам объявить несколько идентификаторов одного и того же типа.
  • Итак, проблема с int я = 0, double j = 2.0; что у вас есть два типа: int и double. Что противоречит [N4140/7.1.6].
  • Это то, что позволяет язык, и, следовательно, приведенное выше утверждение неверно.

Но вы пошли дальше и углубились глубже, и я верю, что ваше замешательство началось здесь дальше: "Поскольку нас интересует только синтаксис формы...". Разве декларация не распалась бы следующим образом?

int i = 0 ::= simple-declaration
Where in...
int ::= type-specifier
i ::= identifier
= 0 ::= init-declarator

Подробнее

Вы упомянули...

Not Allowed: int i, const j;
Allowed: int i, * j;
Not Allowed: int i, const * j; // pointer to const int
Allowed: int const * j, * i = &h;

Мой ответ:

Not Allowed: int i, const j; - because const is a type modifier, syntactically there is no type specified to bind to.
Allowed: int i, * j; - because * grabs the type int and j has a complete type.
Not Allowed: int i, const * j; - because const is not associated to a type here. It is the same problem as in the first case. Read it as j is a pointer to <unexpected word in between> and thus screws up the grammar.
Allowed: int const * j, * i = &h; - because syntactically const has a type to bind to.

"Вопрос в том, почему так?"

  • Когда я учился, CI первоначально был запутан с использованием константы до/после имени типа и для устранения путаницы, я попробовал некоторый тестовый код и понял, что позволяет язык, и следующее: с. Это из моих старых заметок. Это определенно похоже на что-то сделанное новым программистом. Однако он устраняет большинство сомнений.

    [класс хранения] [определитель знака] [определитель размера] [классификаторы типов] < [* [классификаторы типов]] [имя символа] [[] | ([Параметры])] >

    классы хранения: auto, register, static, extern, typedef

    подписывают квалификаторы: signed, unsigned

    Определители размера: короткий, длинный, длинный

    базовые типы: char, int, float, double, void

    классификаторы типов: const, volatile

    имя символа может быть переменной, константой, типом (def) именем и функцией

    A *, префикс к символу, делает его указателем. * может появляться N количество раз, делая его указателем на указатель и т.д.

    A [], прикрепленный к символу, делает его массивом. [] может появиться N раз несколько раз, сделав его многомерным массивом.

    A(), прикрепленный к символу, делает его функцией.() может появляться N раз, но поскольку функция не может вернуть функцию,() может появиться снова, когда функция возвращает указатель на функцию.

Приведенное выше помогло мне сразу думать при объявлении переменных.

Изменение синтаксиса спецификатора типа из моих старых примечаний:

[storage class] [sign qualifier] [size qualifier] <type> [type qualifiers] [* [pointer qualifiers]] [symbol name] [[] | ([parameters])]

Это const и volatile - это либо спецификатор типа, либо определитель указателя, и им нужен тип или указатель для привязки, чтобы квалифицировать их.

Рассмотрим идею "один оператор может разрешить вам только одно объявление, но одно объявление может позволить вам объявить несколько идентификаторов одного типа". Это означает, что синтаксис спецификатора типа можно разбить следующим образом:

type ::= [storage class] [sign qualifier] [size qualifier] <type> [type qualifiers]

symbol ::= [* [pointer qualifiers]] [symbol name] [[] | ([parameters])]

И упрощенным синтаксисом объявления будет:

символ типа [, символ...]

Очевидно, что

int i, const j; - не согласуется с грамматикой.

int const i, j; - согласуется с грамматикой.

Я уверен, что человек, добросовестный со стандартом, может использовать стандарт и предоставлять ответ, используя правильную терминологию и ссылки. Однако имейте в виду, что менее опытные программисты могут найти менее понятный технический ответ.

Если форма "int i, const j" разрешена, тогда можно написать "int const i, const j", и это означает, что j является двойной константой. Это не имеет никакого смысла.

Ответ 2

Вопрос в том, почему это так?

К сожалению, я не могу ответить на это с уверенностью. Я предполагаю, что это короткая рука, которая собиралась сохранить нажатия клавиш на C или одном из ее предшественников. Я скажу, что оператор *, IMHO, меняет тип объявленной переменной и поэтому не знаю, почему это разрешено там, где const нет.

Я также хотел бы добавить пример, который вы не включили, который является законным:

int i = 0, * const j = &i;

И это законно, потому что const в этом случае применяется к *, а не к int. По крайней мере, согласно ideone.

Вероятно, это просто наследие, которое нужно было продвигать.

Ваш отрывок из стандарта проливает свет на тему:

99) A declaration with several declarators is usually equivalent to the corresponding sequence of declarations each with a single declarator. That is

T D1, D2, ... Dn;
is usually equivalent to

T D1; T D2; ... T Dn;

Кажется, что цель состоит в том, что Dn, когда они объявлены вместе, разделенные запятыми, будет одного и того же типа. Поскольку, если вы меняете типы, вы также можете использовать точку с запятой, так как это не спасет вас от нажатия клавиш. I.e.

int i, j = 0; double k, l = 7.3;

Здесь вы сохранили себя, набрав int и double дважды, используя запятую в нужном месте.

Я вижу вашу точку с модификаторами типа const, volatile и т.д. Почему бы не позволить нам объединить их по своему усмотрению? Я имею в виду, как это отличается от *? Я не думаю, что все по-другому, и я надеюсь, что кто-то умнее меня придет и объяснит, почему. Я бы сказал, либо запретил *, либо разрешил нам использовать другие модификаторы.

Итак, , я не знаю, но вот прохладный дополнительный пример сумасшествия:

int i = 0, * const j = &i;

Ответ 3

Вопрос в том, почему это так?

Так как это было в C на рассвете времени (фактически на самом деле 1973) см. здесь

Если это было законно, вы могли бы сделать это для циклов, что несколько полезно.

Это законно, просто не очень красиво:

for(std::tuple<int, double> t = std::make_tuple(0, 10.0) ; 
    std::get<0>(t) < 10 ; 
    ++std::get<0>(t), std::get<1>(t) *= 1.1)
{
    cout << std::get<0>(t) << ", " << std::get<1>(t) << std::endl;
}