我正在更新一个项目以使用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仅在较早的标准版本中允许使用)?
答案 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
的默认构造函数。