I'm trying to compile WebKit with clang, and I'm hitting compile errors due to what is essentially the following pattern:
#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));
}
So, they use std::optional<T>
where T (in their case, WTF::Variant
) has non-trivial copy/move constructors, and then use the std::optional
move constructor. This compiles fine with GCC 8.1.1, but not with clang 6.0.1 (using GCC 8.1.1's 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;
Is this valid C++, or is WebKit broken and clang rightfully rejects this code?
Consider this class:
struct X
{
X(int);
X(X&&) = delete;
// does this need to invoke the move constructor??
X() : X(X(0)) { }
};
According to gcc, the answer is no: this delegates directly to X(int)
. According to clang, the answer is yes, and this fails compilation with:
<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; ^
This seems like potential for a core language issue, since on the one hand [class.base.init]/6 says:
The target constructor is selected by overload resolution. Once the target constructor returns, the body of the delegating constructor is executed.
That is, we specifically talk about picking a constructor and invoking it - which would surely be X(X&&)
in this case. But on the other hand, the very next paragraph says that this is initialization:
The expression-list or braced-init-list in a mem-initializer is used to initialize the designated subobject (or, in the case of a delegating constructor, the complete class object) according to the initialization rules of [dcl.init] for direct-initialization.
and this looks a lot like the guaranteed copy elision example in [dcl.init]/17.6:
If the initializer expression is a prvalue and the cv-unqualified version of the source type is the same class as the class of the destination, the initializer expression is used to initialize the destination object. [ Example:
T x = T(T(T()));
calls theT
default constructor to initializex
. — end example ]
I am not sure which interpretation is correct, but clang rejecting doesn't seem obviously wrong to me.
Why is this example relevant? optional
's move constructor in libstdc++, for types that are trivially destructible and trivially copy/move assignable (like your X
) go through: 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>{}))
{ }
This type has implicitly deleted copy and move constructors, due to having a union with member that isn't trivially copy constructible. So if this delegating constructor has to call the implicit copy constructor (as clang thinks), this is ill-formed. If it doesn't have to, and just either calls one or the other delegated constructor, then this call is fine.