Почему нельзя пересылать объявляемое перечисление?
Название уже есть вопрос.
Более подробная информация: стандартная версия:
Если за последним ключом перечисляется спецификатор вложенных имен, спецификатор перечисления должен ссылаться на перечисление, которое ранее объявлялось непосредственно в классе или пространстве имен, к которому относится спецификатор вложенных имен (т.е. ни унаследованный, ни введенный с помощью объявления-объявления), и спецификатор перечисления должен появиться в пространстве имен включая предыдущую декларацию.
в пункте 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 { }
объявляет все члены класса, что классы С++ "закрыты".
Другими словами, правила для декларирования и определения перечислений элементов с элементами по существу такие же, как для функций-членов или классов-членов.