¿Reglas cambiadas para constructores protegidos en C++ 17?




language-lawyer c++17 (2)

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?


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 .


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 miembros and bases (since C++17) restantes and bases (since C++17) se inicializan by 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.





protected