Почему нельзя пересылать объявляемое перечисление?

Название уже есть вопрос.
Более подробная информация: стандартная версия:

Если за последним ключом перечисляется спецификатор вложенных имен, спецификатор перечисления должен ссылаться на перечисление, которое ранее объявлялось непосредственно в классе или пространстве имен, к которому относится спецификатор вложенных имен (т.е. ни унаследованный, ни введенный с помощью объявления-объявления), и спецификатор перечисления должен появиться в пространстве имен включая предыдущую декларацию.

в пункте 7.2, пункт 4. Например, это запрещает forward-declare перечисление, определенное внутри класса:

struct S{
  enum foo{A, B};
};

теперь S может быть объявлен вперед, а S::foo - нет.

Вопрос о том, почему. Есть ли ситуация, когда это правило может быть выгодным? Зачем это нужно? Или, если вы предпочитаете: если в стандарте не было этого правила, возникает ли ситуация, когда у компилятора возникнет проблема? Какой из них?

Ответ 1

По крайней мере, если forward-declare разрешено перечисление, это создало бы проблемы с специализированными шаблонами, такими как в следующем примере:

// somewhere in a .cpp

template<typename>
struct S;

enum S<int>::E;

// somewhere in a galaxy far, far away

template<typename>
struct S { enum class E {}; };

template<>
struct S<int> {};

Как мог компилятор узнать (и проверить), что enum S<int>::E; действительно определен?


Тем не менее, даже если вы занимаетесь пространством имен, вы не можете этого сделать:

struct X::A;
namespace X { struct A {}; }

Но вы можете сделать это:

namespace X { struct A; }
namespace X { struct A {}; }

Использование классов приведет к созданию кода следующего вида:

struct A { enum E; };
struct A { enum E {} };

В любом случае это нарушит odr, и это не разрешено.


Теперь я попытаюсь дать вам свое представление о том, почему.
Если было разрешено форвардное объявление этого типа, вам разрешили бы дать частичное определение содержащего класса.
Другими словами, рассмотрим следующее: enum S::E. Это твердо заявляет, что S содержит класс enum E, поэтому вы даете ключ к определению S. Чтобы говорить не в стандартном (это далеко не мой естественный язык), вы частично определяете S, поэтому компилятор должен знать, что S имеет свое определение где-то плюс, и он должен иметь определение для E тоже (либо как часть основного определения или как определение вне класса).
Это нарушит правила odr, когда появится реальное определение, поэтому его нельзя разрешить в любом случае, но в качестве исключения из правил основы языка.
Более того, это отличный источник головных болей.

Мои два цента.

Ответ 2

Объект enum объявлен с помощью enum class (или enum struct, а не с struct { enum …). Это будет неперечисленное перечисление в области действия класса.

struct S {
    enum foo {A, B}; // Not a scoped enumeration.
};

Перечисление с областью видимости может быть объявлено вперед внутри класса и определено вне:

struct S {
    enum class foo;
};

enum class S::foo { A, B };

Однако вы не можете объявить члена класса вне класса, если он уже не был объявлен и вы его определяете. Разрешить объявления участников вне противоречит принципу, что определение class { } объявляет все члены класса, что классы С++ "закрыты".

Другими словами, правила для декларирования и определения перечислений элементов с элементами по существу такие же, как для функций-членов или классов-членов.