С++ 11 неоднозначность между одним квалифицированным именем и двумя последовательными?

Является ли следующая программа С++ 11 плохо сформированной?

struct a
{
    struct b {  };

    void f() {};
};

extern struct a b;

struct a ::b;

int main()
{
    b.f();
}

Почему/почему нет?

Интересной здесь является эта строка:

struct a ::b;

Является ли это прямым объявлением внутреннего класса a::b?

Или это определение глобальной переменной b? Эквивалентно:

struct a (::b);

Ответ 1

struct a ::b; не объявляет переменную с именем b типа a, если это то, что вы просите. Это (избыточное) форвардное объявление вложенного типа a::b. Пробелы обычно не значимы в программе на С++. Таким образом, ваша программа объявляет, но никогда не определяет, переменную с именем b. Это нарушение правила одного определения: поэтому программа плохо сформирована, и компоновщик расскажет вам об этом.

Ответ 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;.