Ответ 2
Возьмите 2
struct a ::b;
- это хорошо сформированное объявление struct a::b
.
Это не форвардное объявление, потому что struct a::b
уже
определены. Стандарт формально не определяет форвардную декларацию, но
общепризнанно означает объявление того, что предшествует и
не зависит от его определения.
Это не объявление глобальной переменной b
.
Пробелы несущественны, поэтому для того, чтобы предполагаемая декларация
имеют предлагаемую двусмысленность,
/*A*/ struct a::b;
Разумеется, должен иметь ту же двусмысленность. Переписанный таким образом, мы могли бы
хорошо согласитесь, что совершенно очевидно, что должно означать декларация; но мы бы
как знать, подтверждается ли это очевидное значение Стандартом.
Я ограничу свое рассмотрение стандартом С++ 11. (Я думаю, что резолюция
вопроса из С++ 03 Standard на самом деле более прямолинейно).
Стандарт отличает унарный оператор области ::
от области действия
оператор разрешения ::
.
§ 3.4.3 Поиск квалифицированного имени, пункт 1:
Имя члена класса или элемента пространства имен или перечислителя можно передать после разрешения:: scope оператор (5.1), примененный к вложенному имени-спецификатору, который обозначает его класс, пространство имен или перечисление.
§ 3.4.3 Поиск квалифицированного имени, пункт 4:
Имя, предваряемое оператором унарной области:: (5.1), просматривается в глобальной области, в блоке перевода где он используется.
Эти цитаты не предлагаются, чтобы объяснить различие, просто чтобы показать, что
это существует. Но они доставляют сразу, что в /*A*/
оператор должен быть унарным оператором сферы действия для § 3.4.3/4, чтобы
выдержать глобальную интерпретацию /*A*/
.
Возможно, снова будет очевидно, что оператор области в /*A*/
не является
унарный. Но мы говорим о § 5.1 Первичные выражения для
грамматические определения унарного оператора и сферы охвата
и в любом случае нам еще предстоит увидеть, что говорится о
поиск b
, если if - правый операнд области
оператор разрешения.
В п. 5.1.1/8 находим, что оператор ::
определяется как
инфикс-оператора § 3.4.3/1, а также как унарный оператор
§ 3.4.3/4 в грамматике квалифицированного id:
qualified-id:
nested-name-specifier template[opt] unqualified-id
:: identifier
:: operator-function-id
:: literal-operator-id
:: template-id
nested-name-specifier:
::[opt] type-name ::
::[opt] namespace-name ::
decltype-specifier ::
nested-name-specifier identifier ::
nested-name-specifier template[opt] simple-template-id ::
Это инфиксный оператор, когда он является последним символом
nested-name-specifier, необязательно template
и
то неквалифицированный-id, а в противном случае это унарный оператор в
контексты:
:: identifier
:: operator-function-id
:: literal-operator-id
:: template-id
В соответствии с этой грамматикой оператор ::
должен лежать ассоциированным с
inested-name-specifier (формирование оператора разрешения области), если
оно может. Согласно грамматике, a::
в /*A*/
является спецификатором вложенного имени
и a::b
является квалифицированным идентификатором первой формы
шаблон вложенного имени-спецификатора [opt] unqualified-id.
Таким образом, мы уверены, что § 3.4.3 Подтвержденный поиск имени применяется к b
в
/*A*/
и может ссылаться на § 3.4.3.1 Члены класса, параграф 1,
для вывода о том, что b
в этом контексте нужно искать в a
, а не глобально:
Если спецификатор вложенного имени квалифицированного идентификатора назначает класс, имя, указанное после имени вложенного имени, спецификатор просматривается в рамках класса (10.2), за исключением случаев, перечисленных ниже.
Ни один из "перечисленных ниже случаев" не относится к /*A*/
, и если есть какие-либо сомнения в том, что
вложенный класс a::b
считается членом a
, § 9.2 Члены класса,
пункт 1, удаляет его:
Члены класса являются членами данных, функциями-членами (9.3), вложенными типами и счетчики.
Обнаружив, что a::b
недвусмысленно требует поиска b
в a
,
вопрос о корректности /*A*/
становится вопросом о том,
/*A*/
является объявлением в стандарте (как, безусловно, struct a;
в том же
место будет).
Это, и мы можем убедиться в этом несколькими шагами вниз
Декларация-грамматика: -
-
В § 7 Объявления, параграф 1, декларация может быть простой декларацией,
и простая декларация может быть спецификатором-указателем-seq.
-
В § 7.1 Спецификаторы, параграф 1, spec-specifier-seq может быть спецификатором-указателем,
и spec-specifier может быть спецификатором типа.
-
В соответствии с § 7.1.6 Спецификаторы типов, параграф 1, спецификатор типа может быть
разработаны типа-спецификатор.
-
В соответствии с § 7.1.6.3 Специфицированные спецификаторы типов, para 0, специфицированный спецификатор типа
может быть:
class-key attribute-specifier-seq[opt] nested-name-specifier[opt] identifier
где ключ класса class
, struct
или union
(за § 9 Классы, пункт 1)
и необязательный атрибут-спецификатор-seq не имеет значения, потому что мы не
это нужно.
/*A*/
удовлетворяет:
class-key nested-name-specifier identifier
Итак, это объявление.
В стандарте /*A*/
- это переопределение a::b
.
Некоторые комментарии к GCC и CLANG
Clang 3.3 диагностирует опубликованный код:
error: forward declaration of struct cannot have a nested name specifier
Это неправильно, потому что декларация не является прямой. Если мы переместим нарушителя
так, чтобы оно предшествовало определению a::b
, тогда clang не
вину плохо сформированную декларацию, но вместо этого диагноз:
error: use of undeclared identifier 'a'
И если мы заменим struct a ::b;
в программе на struct a ::b x;
, тогда
clang не находит недостатка в линии, поэтому, найдя определение
a::b
в той же точке, где ранее он считал, что тип (незаконно)
вперед объявлена.
Диагностика GCC 4.8.1:
warning: declaration ‘struct a::b’ does not declare anything
undefined reference to `b'
Предупреждение, являющееся лишь предупреждением, согласуется с выражением
будучи хорошо сформированной декларацией, но может более успешно добавить
слово "новое". Если мы переместим выражение о нарушении, чтобы оно
На самом деле GCC, естественно, дает ту же ошибку, что и clang.
Вторая ошибка привязки относится к undefined extern struct b
который вызывается через main
и который не был правильно поставлен
с определением struct a ::b;
.