您是否应该能够从std :: optional <t>转到T具有非平凡的构造函数的地方?

时间:2018-07-17 10:53:10

标签: c++ webkit c++17 optional

我正在尝试使用clang编译WebKit,并且由于本质上是以下模式,因此我命中了compile errors

#include <iostream>
#include <optional>

struct X {
    X() = default;
    X(const X& other) { }
};

struct Y {
    std::optional<X> x;;
};

int main() {
    Y foo;
    Y bar(std::move(foo));
}

因此,他们使用std::optional<T>,其中T(在他们的情况下为WTF::Variant)具有非平凡的复制/移动构造函数,然后使用std::optional移动构造函数。使用GCC 8.1.1可以很好地进行编译,但是不能使用clang 6.0.1(使用GCC 8.1.1的libstdc ++)进行编译:

In file included from test.cpp:2:
/bin/../lib64/gcc/x86_64-pc-linux-gnu/8.1.1/../../../../include/c++/8.1.1/optional:276:9: error: call to implicitly-deleted copy constructor of 'std::_Optional_payload<X, true, true, true>'
      : _Optional_payload(__engaged
        ^                 ~~~~~~~~~
/bin/../lib64/gcc/x86_64-pc-linux-gnu/8.1.1/../../../../include/c++/8.1.1/optional:739:4: note: in instantiation of member function 'std::_Optional_payload<X, true, true, true>::_Optional_payload' requested here
        : _M_payload(__other._M_payload._M_engaged,
          ^
/bin/../lib64/gcc/x86_64-pc-linux-gnu/8.1.1/../../../../include/c++/8.1.1/optional:985:11: note: in instantiation of member function 'std::_Optional_base<X, false, false>::_Optional_base' requested here
    class optional
          ^
test.cpp:9:8: note: in implicit move constructor for 'std::optional<X>' first required here
struct Y {
       ^
test.cpp:15:7: note: in implicit move constructor for 'Y' first required here
    Y bar(std::move(foo));
      ^
/bin/../lib64/gcc/x86_64-pc-linux-gnu/8.1.1/../../../../include/c++/8.1.1/optional:288:24: note: copy constructor of '_Optional_payload<X, true, true, true>' is implicitly deleted because variant field '_M_payload' has a
      non-trivial copy constructor
          _Stored_type _M_payload;

这是有效的C ++,还是WebKit损坏并且clang正确拒绝了此代码?

1 个答案:

答案 0 :(得分:6)

考虑此类:

struct X
{
    X(int);
    X(X&&) = delete;

    // does this need to invoke the move constructor??
    X() : X(X(0)) { }
};

根据gcc,答案是否定的:这直接委托给X(int)。根据clang的说法,答案是肯定的,并且编译失败:

<source>:55:15: error: call to deleted constructor of 'X'    
        X() : X(X(0)) { }   
              ^ ~~~~    
<source>:52:9: note: 'X' has been explicitly marked deleted here    
        X(X&&) = delete;   
        ^

这似乎是解决核心语言问题的潜力,因为一方面[class.base.init]/6说:

  

通过重载分辨率选择目标构造函数。目标构造函数返回后,将委派构造函数的主体。

也就是说,我们专门讨论选择构造函数并调用它-在这种情况下肯定是X(X&&)。但是另一方面,the very next paragraph说这是初始化:

  

mem-initializer 中的 expression-list braced-init-list 用于初始化指定的子对象(或者,在如果是委派构造函数,则根据[dcl.init]的初始化规则进行直接初始化。

这看起来很像[dcl.init]/17.6中的保证复制省略示例:

  

如果初始化程序表达式是prvalue,并且源类型的cv不合格版本与目标程序的类相同,则使用初始化程序表达式初始化目标对象。 [示例: T x = T(T(T()));调用T的默认构造函数来初始化x-示例]

我不确定哪种解释正确,但是拒绝拒绝似乎对我来说显然不是错误的。


为什么这个例子有意义? optional是libstdc ++中的move构造函数,对于那些可微破坏和可复制/可移动分配的类型(例如您的X),请执行以下操作:this constructor

  constexpr
  _Optional_payload(bool __engaged, _Optional_payload&& __other)
  : _Optional_payload(__engaged
          ? _Optional_payload(__ctor_tag<bool>{},
                      std::move(__other._M_payload))
          : _Optional_payload(__ctor_tag<void>{}))
  { }

此类型已隐式删除了copy和move构造函数,这是因为与成员的联合并不容易实现。因此,如果此委派构造函数必须调用隐式副本构造函数(如clang所认为的那样),则格式错误。如果没有必要,只需调用一个或另一个委托的构造函数,则此调用就可以了。