Подтвердить что ты не робот

Объявления в С++

Из того, что я понял, объявления/инициализации в С++ представляют собой инструкции с "базовым типом", за которыми следует разделенный запятыми список деклараторов.

Рассмотрим следующие объявления:

int i = 0, *const p = &i; // Legal, the so-called base type is 'int'.
                          // i is an int while p is a const pointer to an int.

int j = 0, const c = 2;   // Error: C++ requires a type specifier for all declarations.
                          // Intention was to declare j as an int and c an as const int.

int *const p1 = nullptr, i1 = 0; // p1 is a const pointer to an int while i1 is just an int.

int const j1 = 0, c1 = 2;   // Both j1 and c1 are const int.

Является ли const int базовым типом или составным типом?

Из ошибки во втором объявлении выше, он кажется базовым. Если это так, то как насчет первой декларации?

Иными словами, если первое утверждение является законным, почему не второе? Кроме того, почему поведение отличается от третьего и четвертого утверждений?

4b9b3361

Ответ 1

Хороший вопрос, со сложным ответом. Чтобы понять это, вам нужно полностью понять внутреннюю структуру объявлений на С++.

(Обратите внимание, что в этом ответе я полностью опускаю существование атрибутов, чтобы предотвратить чрезмерное применение).

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

Спецификаторы:

  • спецификаторы класса хранения (например, static, extern)
  • спецификаторы функций (например, virtual, inline)
  • friend, typedef, constexpr
  • спецификаторы типов, которые включают:
    • спецификаторы простого типа (например, int, short)
    • cv-qualifiers (const, volatile)
    • другие вещи (например, decltype)

Вторая часть объявления представляет собой разделяемые запятой init-declarators. Каждый init-declarator состоит из последовательности деклараторов, необязательно сопровождаемых инициализатором.

Какие деклараторы:

  • (например, i in int i;)
  • операторы, подобные указателям (*, &, &&, синтаксис указателя на элемент)
  • синтаксис параметра функции (например, (int, char))
  • синтаксис массива (например, [2][3])
  • cv-qualifiers, если они следуют за указателем указателя.

Обратите внимание, что структура декларации строгая: сначала спецификаторы, а затем init-declarators (каждый из которых является деклараторами, а затем инициатором).

Это правило: спецификаторы применяются ко всему объявлению, а деклараторы применяются только к одному init-declarator (к одному элементу списка, разделенного запятыми).

Также обратите внимание на то, что cv-квалификатор может использоваться как спецификатор, так и декларатор. В качестве декларатора грамматика ограничивает их использование только при наличии указателей.

Итак, для обработки четырех объявлений, которые вы разместили:

1

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

Часть спецификатора содержит только один спецификатор: int. Это та часть, к которой будут применяться все деклараторы.

Есть два init-декларатора: i = 0 и * const p = &i.

Первый имеет один декларатор i и инициализатор = 0. Поскольку не существует декларатора модификации типов, тип i задается спецификаторами int в этом случае.

Второй init-declarator имеет три объявления: *, const и p. И инициализатор = &i.

Объявители * и const изменяют базовый тип на "постоянный указатель на базовый тип". Базовый тип, заданный спецификаторами, равен int, типу p будет "постоянный указатель на int".

2

int j = 0, const c = 2;

Опять же, один спецификатор: int и два init-declarators: j = 0 и const c = 2.

Для второго init-declarator деклараторы const и c. Как я уже упоминал, грамматика разрешает только cv-qualifiers как деклараторы, если есть указатель. Это не так, следовательно, ошибка.

3

int *const p1 = nullptr, i1 = 0;

Один спецификатор: int, два init-declarators: * const p1 = nullptr и i1 = 0.

Для первого init-declarator объявления: *, const и p1. Мы уже рассматривали такой init-declarator (второй в случае 1). Он добавляет "постоянный указатель на базовый тип" к определяемому спецификатором базовому типу (который все еще int).

Для второго init-declarator i1 = 0 это очевидно. Нет модификаций типа, используйте спецификаторы (-и) как есть. Таким образом, i1 становится int.

4

int const j1 = 0, c1 = 2;

Здесь мы имеем принципиально другую ситуацию из предыдущих трех. У нас есть два спецификатора: int и const. А затем два init-declarators, j1 = 0 и c1 = 2.

Ни один из этих инициаторов инициализации не имеет в них деклараторов, изменяющих тип, поэтому они оба используют тип из спецификаторов, который равен const int.

Ответ 2

Это указано в [dcl.dcl] и [dcl.decl] как часть simple-declaration * и сводится к различиям между ветвями в ptr-declarator:

declaration-seq:
    declaration

declaration:
    block-declaration

block-declaration:
    simple-declaration

simple-declaration:
    decl-specifier-seqopt init-declarator-listopt ;
----

decl-specifier-seq:
    decl-specifier decl-specifier-seq    

decl-specifier:    
    type-specifier                               ← mentioned in your error

type-specifier:
    trailing-type-specifier

trailing-type-specifier:
    simple-type-specifier
    cv-qualifier
----

init-declarator-list:
   init-declarator
   init-declarator-list , init-declarator

init-declarator:
   declarator initializeropt

declarator:
    ptr-declarator

ptr-declarator:                                 ← here is the "switch"
    noptr-declarator
    ptr-operator ptr-declarator

ptr-operator:                                   ← allows const
    *  cv-qualifier-seq opt

cv-qualifier:
    const
    volatile

noptr-declarator:                               ← does not allow const
    declarator-id

declarator-id:
    id-expression

Важная fork в правилах находится в ptr-declarator:

ptr-declarator:
    noptr-declarator
    ptr-operator ptr-declarator

По существу, noptr-declarator в вашем контексте - это только id-expression. Он не может содержать никаких cv-qualifier, но квалифицированных или неквалифицированных идентификаторов. Однако a ptr-operator может содержать cv-qualifier.

Это указывает на то, что ваш первый оператор отлично действует, поскольку ваш второй init-declarator

 *const p = &i;

является ptr-declarator формы ptr-operator ptr-declarator, при этом ptr-operator является * const в этом случае, а ptr-declarator является неквалифицированным идентификатором.

Ваш второй оператор не является законным, поскольку он недействителен ptr-operator:

 const c = 2

A ptr-operator должен начинаться с *, &, && или вложенного спецификатора имен, за которым следует *. Так как const c не начинается с любого из этих токенов, рассмотрим const c как noptr-declarator, что не позволяет здесь const.

Кроме того, почему поведение отличается от 3-го и 4-го операторов?

Потому что int является type-specifier, а * является частью init-declarator,

*const p1

объявляет константный указатель.

Однако в int const имеем a decl-specifier-seq двух decl-specifier, int (a simple-type-specifier) и const (a cv-qualifier), см. trailing-type-specifier. Поэтому оба формируют один спецификатор декларации.


* Примечание. Я пропустил все альтернативы, которые здесь не могут быть применены и упрощены некоторые правила. Подробнее см. Раздел 7 "Объявления" и раздел 8 "Деклараторы" С++ 11 (n3337).