¿Reglas cambiadas para constructores protegidos en C++ 17?
language-lawyer c++17 (2)
En C ++ 17, las reglas sobre los agregados han cambiado.
Por ejemplo, puede hacer esto en C ++ 17 ahora:
struct A { int a; };
struct B { B(int){} };
struct C : A {};
struct D : B {};
int main() {
(void) C{2};
(void) D{1};
}
Tenga en cuenta que no estamos heredando el constructor.
En C ++ 17,
C
y
D
ahora son agregados, incluso si tienen clases base.
Con
{}
, la inicialización agregada se activa y el envío de ningún parámetro se interpretará de la misma manera que se llama al constructor predeterminado del padre desde el exterior.
Por ejemplo, la inicialización agregada se puede deshabilitar cambiando la clase
D
a esto:
struct B { protected: B(){} };
struct D : B {
int b;
private:
int c;
};
int main() {
(void) D{}; // works!
}
Esto se debe a que la inicialización agregada no se aplica cuando se tienen miembros con diferentes especificadores de acceso.
La razón por la que with
= default
funciona es porque no es un constructor proporcionado por el usuario.
Más información en
esta pregunta
.
Tengo este caso de prueba:
struct A{ protected: A(){} };
struct B: A{};
struct C: A{ C(){} };
struct D: A{ D() = default; };
int main(){
(void)B{};
(void)C{};
(void)D{};
}
Tanto gcc como clang lo compilan en modo C ++ 11 y C ++ 14. Ambos fallan en modo C ++ 17:
$ clang++ -std=c++17 main.cpp
main.cpp:7:10: error: base class ''A'' has protected default constructor
(void)B{};
^
main.cpp:1:22: note: declared protected here
struct A{ protected: A(){} };
^
main.cpp:9:10: error: base class ''A'' has protected default constructor
(void)D{};
^
main.cpp:1:22: note: declared protected here
struct A{ protected: A(){} };
^
2 errors generated.
$ clang++ --version
clang version 6.0.0 (http://llvm.org/git/clang.git 96c9689f478d292390b76efcea35d87cbad3f44d) (http://llvm.org/git/llvm.git 360f53a441902d19ce27d070ad028005bc323e61)
Target: x86_64-unknown-linux-gnu
Thread model: posix
(Clang compilado de Master Branch 2017-12-05.)
$ g++ -std=c++17 main.cpp
main.cpp: In function ''int main()'':
main.cpp:7:10: error: ''A::A()'' is protected within this context
(void)B{};
^
main.cpp:1:22: note: declared protected here
struct A{ protected: A(){} };
^
main.cpp:9:10: error: ''A::A()'' is protected within this context
(void)D{};
^
main.cpp:1:22: note: declared protected here
struct A{ protected: A(){} };
^
$ g++ --version
g++ (GCC) 8.0.0 20171201 (experimental)
Copyright (C) 2017 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
¿Es este cambio de comportamiento parte de C ++ 17 o es un error en ambos compiladores?
La definición de aggregate cambió desde C ++ 17.
Antes de C ++ 17
sin clases base
Desde C ++ 17
sin clases base
virtual, private, or protected (since C++17)
Eso significa que, para
B
y
D
, no son de tipo agregado antes de C ++ 17, luego para
B{}
y
D{}
, se realizará la
value-initialization
, luego se llamará al
constructor predeterminado predeterminado
;
lo cual está bien, porque el constructor de clase derivada podría llamar al constructor
protected
de la clase base.
Desde C ++ 17,
B
y
D
convierten en tipo agregado (porque solo tienen una clase base
public
, y tenga en cuenta que para la clase
D
, el constructor predeterminado explícitamente predeterminado está permitido para el tipo agregado desde C ++ 11), luego para
B{}
y
D{}
, se realizará
aggregate-initialization
,
Cada elemento de
direct public base, (since C++17)
, o miembro de clase no estático, en orden de subíndice / apariencia de la matriz en la definición de clase, se inicializa mediante copia desde la cláusula correspondiente de la lista de inicializadores.Si el número de cláusulas de inicializador es menor que el número de miembros
and bases (since C++17)
o la lista de inicializadores está completamente vacía, los miembrosand bases (since C++17)
restantesand bases (since C++17)
se inicializanby their default initializers, if provided in the class definition, and otherwise (since C++14)
mediante listas vacías, de acuerdo con las reglas habituales de inicialización de listas (que realiza la inicialización de valores para tipos que no son de clase y clases no agregadas con constructores predeterminados, e inicialización agregada para agregados). Si un miembro de un tipo de referencia es uno de estos miembros restantes, el programa está mal formado.
Eso significa que el subobjeto de la clase base se inicializará directamente con el valor, se omitirá el constructor de
B
y
D
;
pero el constructor predeterminado de
A
está
protected
, entonces el código falla.
(Tenga en cuenta que
A
no es de tipo agregado porque tiene un constructor proporcionado por el usuario).
Por cierto:
C
(con un constructor proporcionado por el usuario) no es un tipo agregado antes y después de C ++ 17, por lo que está bien para ambos casos.