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

Jas*_*n R 46 c++ clang boost-variant c++17

我正在更新一个项目以使用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);
}
Run Code Online (Sandbox Code Playgroud)

在C ++ 17模式下使用clang v8.0,此操作失败,并显示以下错误

<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;
Run Code Online (Sandbox Code Playgroud)

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

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

Bar*_*rry 59

铛在这里是正确的。这是一个简化的示例:

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

struct D : B { };

auto d = D{};
Run Code Online (Sandbox Code Playgroud)

在C ++ 14中,D不是聚合类,因为它具有基类,D{}“普通”(非聚合)初始化也是如此,它调用D的默认构造函数,而后者又调用B的默认构造函数。可以,因为D可以访问B的默认构造函数。

在C ++ 17中,对聚合的定义进行了扩展-现在允许基类(只要它们不是virtual)。D现在是一个聚合,这意味着D{}是聚合初始化。在聚合初始化中,这意味着我们(调用方)正在初始化所有子对象-包括基类子对象。但是我们无权访问B的构造函数(它是protected),因此我们无法调用它,因此它的格式不正确。


不要害怕,修复很容易。使用括号:

auto d = D();
Run Code Online (Sandbox Code Playgroud)

这可以D像以前一样调用默认的构造函数。

  • 完美的,这就是我一直在寻找的:聚合的扩展定义是罪魁祸首。 (6认同)
  • 我听说括号和花括号在C ++ 20中是统一的。这会影响修复程序的有效性吗? (2认同)
  • 减少有点错误。C++17 中的问题是 dtor(`static_visitor` 本身就是一个 C++17 聚合,因此 ctor 的保护性并不重要)。在 C++20 中,两者都是问题,并且 boost 修复(仅修复了 dtor)将再次中断,这使得这是 C++20 的一个很好的示例 OMG-这些人为的示例非常令人惊讶-我们必须修复它,改变破坏性的东西。 (2认同)