此代码是否应该无法在C ++ 17中编译?

时间:2019-05-29 19:43:21

标签: c++ clang c++17 boost-variant

我正在更新一个项目以使用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);
}

Using clang v8.0 in C++17 mode, this fails with the following error

<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;

但是,it compiles cleanly in C++14 mode。我发现如果将花括号初始化vis{}更改为括号vis(),则可以在两种模式下正确编译。我尝试过的每个版本的gcc都允许在C ++ 17模式下使用这两种变体。

这是从C ++ 14到C ++ 17的正确行为更改,还是这是一个叮叮当当的错误?如果正确,为什么现在在C ++ 17中无效(或者也许一直如此,但是clang仅在较早的标准版本中允许使用)?

1 个答案:

答案 0 :(得分:48)

在这里

clang是正确的。这是一个简化的示例:

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的默认构造函数。