Должен ли этот код не скомпилироваться в С++ 17?
Я обновлял проект для использования С++ 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 в режиме С++ 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;
Тем не менее, он компилируется чисто в режиме С++ 14. Я обнаружил, что если я изменю инициализацию фигурной скобки vis{}
на скобки vis()
, то она корректно компилируется в обоих режимах. Каждая пробная версия gcc допускает оба варианта в режиме С++ 17.
Это правильное изменение в поведении с С++ 14 на С++ 17, или это ошибка лягушки? Если это правильно, почему это теперь недопустимо в С++ 17 (или, возможно, так было всегда, но clang просто позволяет это в более ранних стандартных версиях)?
Ответы
Ответ 1
лязг здесь прав. Вот сокращенный пример:
struct B {
protected:
B() { }
};
struct D : B { };
auto d = D{};
В С++ 14 D
не является агрегатом, потому что он имеет базовый класс, поэтому D{}
- это "нормальная" (non- агрегатная) инициализация, которая вызывает конструктор D
умолчанию, который, в свою очередь, вызывает конструктор B
умолчанию. Это нормально, потому что D
имеет доступ к конструктору B
умолчанию.
В С++ 17 определение агрегата было расширено - теперь разрешены базовые классы (если они non- virtual
). D
теперь является агрегатом, что означает, что D{}
является агрегатной инициализацией. И в агрегатной инициализации это означает, что мы (вызывающая сторона) инициализируем все подобъекты - включая подобъект базового класса. Но у нас нет доступа к конструктору B
(он protected
), поэтому мы не можем вызвать его, так как он некорректно сформирован.
Не бойся, исправить это легко. Используйте скобки:
auto d = D();
Это возвращает нас к вызову конструктора D
умолчанию, как и раньше.