Будет ли класс, объявленный в инициализаторе члена конструктора другого класса, видимым за его пределами?
Рассмотрим следующий фрагмент кода:
struct Foo {
void* p;
Foo() : p{(class Bar*)0} {}
};
Bar* bar;
Последние версии GCC (8.2) и Clang (7.0.0) не могут его скомпилировать. То же самое делает ICC (19.0.1).
Однако MSVC (v19.16) компилирует его чисто.
Ошибка от GCC: error: 'Bar' does not name a type; did you mean 'char'?
error: 'Bar' does not name a type; did you mean 'char'?
Clang и ICC выдают похожие сообщения.
Просмотрщик соответствия для всех четырех компиляторов в Godbolt.
Итак, какой из компиляторов верен в соответствии со стандартом?
Ответы
Ответ 1
-
[basic.lookup.elab]... Если описатель типа введен с помощью ключа класса, и этот поиск не находит ранее объявленное имя типа... указатель разработанного типа является объявлением, которое вводит имя класса, как описано в [basic.scope.pdecl]
-
[basic.scope.pdecl] - для подробного спецификатора типа формы
идентификатор класса
если разработанный спецификатор типа используется в выражении decl-specier-seq или параметр-объявления функции, определенной в области пространства имен [не применяется из-за области действия],... в противном случае, за исключением объявления друга, идентификатор объявлен в наименьшем пространстве имен или области блока, которая содержит объявление.
Теперь немного хитрый. Список инициализаторов членов "содержится" в области конструктора или нет? Если нет, то наименьшая область блока или пространства имен - это глобальное пространство имен, и программа будет правильно сформирована. Если да, то наименьшая область действия - это область блока конструктора, и поэтому класс не будет объявлен в глобальной области.
Насколько я могу судить, нет правила, гласящего, что mem-init-list "содержится в области видимости конструктора". Это находится вне фигурных скобок, которые ограничивают область. Таким образом, программа хорошо сформирована.
Mem-init-list является частью тела конструктора [dcl.fct.def.general], но это тело не является ни областью блока, ни областью пространства имен, поэтому оно не относится к правилу в [basic.scope.pdecl],
Ответ 2
Это о правилах декларации и правилах области видимости; (разработанный спецификатор типа может также объявить имя класса в выражении).
scope.declarative/3: имена, объявленные объявлением, вводятся в область, в которой происходит объявление, за исключением того, что присутствие спецификатора друга, определенные использования разработанного спецификатора типа ([dcl.type.elab]) и using-директивы ([namespace.udir]) изменяют это общее поведение.
у конструктора есть область (как упоминается в ответе eeroika), аналогично всему, что там заявлено. Вот почему это должно быть в силе
struct Foo {
void* p;
Foo() : p{(class Bar*)0} {
Bar* bar;
}
};
https://godbolt.org/z/m3Tdle
Но нет:
struct Foo {
void* p;
Foo() : p{(class Bar*)0} {
//Scope of Bar only exists here
}
};
Bar* bar;
РЕДАКТИРОВАТЬ: Подробнее об этом:
Так называемое "предварительное объявление" в C++ технически является одной из множества "форм" elaborated-type-specifier
; И только две формы могут вводить имя. (Акцент мой)
Точка объявления класса, впервые объявленного в подробном спецификаторе типа, следующая:
-
(7.1) для декларации формы
ключ-атрибут-спецификатор-seq opt- идентификатор; //<-- Note the semi colon
идентификатор объявляется как имя класса в области, содержащей объявление, в противном случае
-
(7.2) для уточненного спецификатора типа формы
идентификатор класса
если разработанный спецификатор типа используется в выражении decl-specier-seq или параметр-объявление функции, определенной в области пространства имен, идентификатор объявляется как имя класса в пространстве имен, которое содержит объявление; в противном случае, за исключением объявления друга, идентификатор объявляется в наименьшем пространстве имен или области блока, содержащей объявление. [Примечание: эти правила также применяются в шаблонах. - примечание конца] [Примечание: Другие формы разработанного спецификатора типа не объявляют новое имя, и поэтому должны ссылаться на существующее имя типа. Смотрите [basic.lookup.elab] и [dcl.type.elab]. —- конец примечания]
Вот еще один интересный момент об этом;
dcl.type.elab/1 Атрибут-спецификатор-seq не должен появляться в подробном спецификаторе типа, если только последний не является единственной составляющей объявления...
Вот почему это (ниже) действительно, смотрите его здесь https://godbolt.org/z/IkmvGn;
void foo(){
void* n = (class Boo*)(0);
Boo* b;
}
class [[deprecated("WTF")]] Mew;
Но это (ниже) неверно 1; Смотрите его здесь https://godbolt.org/z/8X1QKq;
void foo(){
void* n = (class [[deprecated("WTF")]] Boo*)(0);
}
class [[deprecated("WTF")]] Mew;
-
1 странно, GCC принимает это, но дает это предупреждение:
attributes ignored on elaborated-type-specifier that is not a forward declaration [-Wattributes]
Чтобы увидеть все другие "формы" elaborated-type-specifier
смотрите dcl.type.elab
Редактировать 2
Чтобы еще раз проверить мое понимание, я спросил еще, и @Simon Brand заставил меня осознать некоторые крайние случаи, на что частично намекает basic.scope/Declarative-4.note-2. Но в основном по второй цитате в этом ответе basic.scope/pdecl-7.2
Сложный спецификатор типа в пределах области параметров функции будет просачивать свое имя класса во вмещающее пространство имен (примечание: это не область действия класса).
Это действительно https://godbolt.org/z/Fx5B83:
struct Foo {
static void foo(void* = (class Bat*)0); //Leaks it past class-scope
};
void moo(){
Bat* m;
}
Даже если вы прячете его во вложенные классы https://godbolt.org/z/40Raup:
struct Foo {
class Moo{
class Mew{
void foo(void* = (class Bat*)0); //
};
};
};
void moo(){
Bat* m;
}
... Однако имя перестает вытекать в ближайшем namespace
https://godbolt.org/z/YDljDo.
namespace zoo {
struct Foo {
class Moo{
class Mew{
void foo(void* = (class Bat*)0);
};
};
};
}
void moo(){
zoo::Bat* m;
}
В заключение,
mem-init-list является составным оператором и не имеет функции-параметра-области видимости; Итак, если вы хотите добиться утечки, сделайте объявление в function-parameter-scope. Смотрите https://godbolt.org/z/CqejYS
struct Foo {
void* p;
Foo(void* = (class Zoo*)(0))
: p{(class Bar*)0} {
Bar* bar;
}
};
Zoo* m; //Zoo* leaked
//Bar* n //Bar* did not leak