Должен ли этот код не скомпилироваться в C ++ 17? - PullRequest
46 голосов
/ 29 мая 2019

Я обновлял проект для использования C ++ 17 и обнаружил несколько случаев, когда код, следовавший этому шаблону, вызывал ошибку компиляции в последних версиях clang:

#include <boost/variant.hpp>

struct vis : public boost::static_visitor<void>
{
    void operator()(int) const { }
};

int main()
{
    boost::variant<int> v = 0;
    boost::apply_visitor(vis{}, v);
}

При использовании clang v8.0 в режиме C ++ 17 происходит сбой со следующей ошибкой :

<source>:11:30: error: temporary of type 'boost::static_visitor<void>' has protected destructor
    boost::apply_visitor(vis{}, v);
                             ^
/opt/compiler-explorer/libs/boost_1_64_0/boost/variant/static_visitor.hpp:53:5: note: declared protected here
    ~static_visitor() = default;

Однако, компилируется чисто в режиме C ++ 14 . Я обнаружил, что если я изменю инициализацию скобки vis{} на круглые скобки vis(), то она корректно компилируется в обоих режимах. Каждая пробная версия gcc допускает оба варианта в режиме C ++ 17.

Это правильное изменение в поведении с C ++ 14 на C ++ 17, или это ошибка clang? Если это правильно, почему теперь это недопустимо в C ++ 17 (или, возможно, так было всегда, но clang просто позволяет это в более ранних стандартных версиях)?

1 Ответ

58 голосов
/ 29 мая 2019

лязг здесь правильный. Вот сокращенный пример:

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

struct D : B { };

auto d = D{};

В C ++ 14 D является , а не агрегатом, поскольку имеет базовый класс, поэтому D{} является "нормальной" (неагрегированной) инициализацией, которая вызывает D конструктор по умолчанию, который в свою очередь вызывает конструктор по умолчанию B. Это нормально, потому что D имеет доступ к конструктору B по умолчанию.

В C ++ 17 определение агрегата было расширено - теперь разрешены базовые классы (если они не virtual). D теперь является агрегатом, что означает, что D{} является агрегатной инициализацией. И в агрегатной инициализации это означает, что мы (вызывающая сторона) инициализируем все подобъекты - включая подобъект базового класса. Но мы не имеем доступ к конструктору B (это protected), поэтому мы не можем вызвать его, так как он некорректно сформирован.

<ч />

Не бойся, исправить легко. Используйте скобки:

auto d = D();

Это возвращает нас к вызову конструктора D по умолчанию, как и раньше.

...