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?
std::move is actually just a request to move and if the type of the object has not a move constructor/assign-operator defined or generated the move operation will fall back to a copy.
std::move itself does "nothing" - it has zero side effects. It just signals to the compiler that the programmer doesn't care what happens to that object any more. i.e. it gives permission to other parts of the software to move from the object, but it doesn't require that it be moved.
The class template std::optional manages an optional contained value, i.e. a value that may or may not be present. A common use case for optional is the return value of a function that may fail.
A move constructor enables the resources owned by an rvalue object to be moved into an lvalue without copying.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With