Подтвердить что ты не робот

Изменены правила для защищенных конструкторов в С++ 17?

У меня есть этот тестовый пример:

struct A{ protected: A(){} };
struct B: A{};
struct C: A{ C(){} };
struct D: A{ D() = default; };

int main(){
    (void)B{};
    (void)C{};
    (void)D{};
}

Оба gcc и clang компилируют его в режиме С++ 11 и С++ 14. Оба не работают в режиме С++ 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, скомпилированный из главного ветки 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.

Является ли это изменение поведения частью С++ 17 или это ошибка в обоих компиляторах?

4b9b3361

Ответ 1

Определение aggregate изменилось с С++ 17.

До С++ 17

нет базовых классов

Так как С++ 17

no virtual, private, or protected (since C++17) базовые классы

Это означает, что для B и D они не являются агрегатными типами до С++ 17, затем для B{} и D{}, инициализация значений будет выполнена, тогда будет вызываться дефолтный конструктор по умолчанию; что прекрасно, потому что конструктор protected базового класса может быть вызван конструктором производного класса.

Так как С++ 17, B и D становятся агрегированными типами (потому что они имеют только базовый класс public и отмечают, что для класса D явно установленный по умолчанию конструктор по умолчанию разрешен для типа агрегата, поскольку С++ 11), то для B{} и D{} будет выполняться агрегатная инициализация,

Каждый элемент массива direct public base, (since C++17) или нестатический член класса в порядке индекса/внешнего вида массива в определении класса инициализируется копией из соответствующего предложения списка инициализаторов.

Если количество предложений инициализатора меньше количества членов and bases (since C++17) или список инициализаторов полностью пуст, остальные члены and bases (since C++17) инициализируются by their default initializers, if provided in the class definition, and otherwise (since C++14) пустыми списками в соответствии с обычными правилами инициализации списка (который выполняет инициализацию значений для типов неклассов и неагрегатных классов с конструкторами по умолчанию и агрегатной инициализацией для агрегатов). Если членом ссылочного типа является один из этих оставшихся членов, программа плохо сформирована.

Это означает, что подобъект базового класса будет инициализирован инициализацией значения, конструктор B и D обходит; но конструктор по умолчанию A равен protected, тогда код выходит из строя. (Обратите внимание, что A не является агрегированным типом, потому что он имеет предоставленный пользователем конструктор.)

BTW: C (с предоставленным пользователем конструктором) не является агрегированным типом до и после С++ 17, поэтому он отлично подходит для обоих случаев.

Ответ 2

В С++ 17 правила об агрегатах изменились.

Например, вы можете сделать это сейчас на С++ 17:

struct A { int a; };
struct B { B(int){} };

struct C : A {};
struct D : B {};

int main() {
    (void) C{2};
    (void) D{1};
}

Обратите внимание, что мы не наследуем конструктор. В С++ 17 C и D теперь являются агрегатами, даже если они имеют базовые классы.

С {}, инициализация агрегата вступает в силу и не отправляет никаких параметров, будет интерпретироваться так же, как вызов родительского конструктора по умолчанию извне.

Например, инициализацию агрегата можно отключить, изменив класс D на это:

struct B { protected: B(){} };

struct D : B {
    int b;
private:
    int c;
};

int main() {
    (void) D{}; // works!
}

Это связано с тем, что инициализация агрегата не применяется при наличии членов с разными спецификаторами доступа.

Причина, по которой работает = default, заключается в том, что она не является предоставленным пользователем конструктором. Больше информации на этот вопрос.