I don't see anything on subj in current draft. Do I get it right, that the following code
struct Omg { Omg &operator=(Omg const &o) { throw 0; } };
std::tuple t0{42, Omg{}};
std::tuple t1{10, Omg{}};
t1 = t0;
is fully allowed to leave t1 in semi-assigned state? I.e., its first element could have already changed yet the second one can remain as it was, or even become inconsistent?
is fully allowed to leave t1 in semi-assigned state?
Yes. Copy-assignment is specified as just:
Effects: Assigns each element of u to the corresponding element of *this.
There are other types in the standard library that do specify an exception guarantee (e.g. optional), but tuple does not provide one.
Note that it doesn't specify an ordering to the assignment. An implementation could assign the Omg first (so no change to t1) or the int first (so you end up with a semi-assigned state).
I think an implementation could also choose to do copy-and-swap and thus provide a strong exception guarantee. That would match the specified effects. But this is not guaranteed by the standard.
Related
I recently followed a Reddit discussion which lead to a nice comparison of std::visit optimization across compilers. I noticed the following: https://godbolt.org/z/D2Q5ED
Both GCC9 and Clang9 (I guess they share the same stdlib) do not generate code for checking and throwing a valueless exception when all types meet some conditions. This leads to way better codegen, hence I raised an issue with the MSVC STL and was presented with this code:
template <class T>
struct valueless_hack {
struct tag {};
operator T() const { throw tag{}; }
};
template<class First, class... Rest>
void make_valueless(std::variant<First, Rest...>& v) {
try { v.emplace<0>(valueless_hack<First>()); }
catch(typename valueless_hack<First>::tag const&) {}
}
The claim was, that this makes any variant valueless, and reading the docu it should:
First, destroys the currently contained value (if any). Then
direct-initializes the contained value as if constructing a value of
type T_I with the arguments std::forward<Args>(args).... If an
exception is thrown, *this may become valueless_by_exception.
What I don't understand: Why is it stated as "may"? Is it legal to stay in the old state if the whole operation throws? Because this is what GCC does:
// For suitably-small, trivially copyable types we can create temporaries
// on the stack and then memcpy them into place.
template<typename _Tp>
struct _Never_valueless_alt
: __and_<bool_constant<sizeof(_Tp) <= 256>, is_trivially_copyable<_Tp>>
{ };
And later it (conditionally) does something like:
T tmp = forward(args...);
reset();
construct(tmp);
// Or
variant tmp(inplace_index<I>, forward(args...));
*this = move(tmp);
Hence basically it creates a temporary, and if that succeeds copies/moves it into the real place.
IMO this is a violation of "First, destroys the currently contained value" as stated by the docu. As I read the standard, then after a v.emplace(...) the current value in the variant is always destroyed and the new type is either the set type or valueless.
I do get that the condition is_trivially_copyable excludes all types that have an observable destructor. So this can also be though as: "as-if variant is reinitialized with the old value" or so. But the state of the variant is an observable effect. So does the standard indeed allow, that emplace does not change the current value?
Edit in response to a standard quote:
Then initializes the contained value as if direct-non-list-initializing a value of type TI with the arguments std::forward<Args>(args)....
Does T tmp {std::forward<Args>(args)...}; this->value = std::move(tmp); really count as a valid implementation of the above? Is this what is meant by "as if"?
I think the important part of the standard is this:
From https://timsong-cpp.github.io/cppwp/n4659/variant.mod#12
23.7.3.4 Modifiers
(...)
template
variant_alternative_t>& emplace(Args&&... args);
(...) If an exception is thrown during the initialization of the contained value, the variant might not hold a value
It says "might" not "must". I would expect this to be intentional in order to allow implementations like the one used by gcc.
As you mentioned yourself, this is only possible if the destructors of all alternatives are trivial and thus unobservable because destroying the previous value is required.
Followup question:
Then initializes the contained value as if direct-non-list-initializing a value of type TI with the arguments std::forward<Args>(args)....
Does T tmp {std::forward(args)...}; this->value = std::move(tmp); really count as a valid implementation of the above? Is this what is meant by "as if"?
Yes, because for types that are trivially copyable there is no way to detect the difference, so the implementation behaves as if the value was initialized as described. This would not work if the type was not trivially copyable.
So does the standard indeed allow, that emplace does not change the
current value?
Yes. emplace shall provide the basic guarantee of no leaking (i.e., respecting object lifetime when construction and destruction produce observable side effects), but when possible, it is allowed to provide the strong guarantee (i.e., the original state is kept when an operation fails).
variant is required to behave similarly to a union — the alternatives are allocated in one region of suitably allocated storage. It is not allowed to allocate dynamic memory. Therefore, a type-changing emplace has no way to keep the original object without calling an additional move constructor — it has to destroy it and construct the new object in place of it. If this construction fails, then the variant has to go to the exceptional valueless state. This prevents weird things like destroying a nonexistent object.
However, for small trivially copyable types, it is possible to provide the strong guarantee without too much overhead (even a performance boost for avoiding a check, in this case). Therefore, the implementation does it. This is standard-conforming: the implementation still provides the basic guarantee as required by the standard, just in a more user-friendly way.
Edit in response to a standard quote:
Then initializes the contained value as if
direct-non-list-initializing a value of type TI with the arguments
std::forward<Args>(args)....
Does T tmp {std::forward<Args>(args)...}; this->value =
std::move(tmp); really count as a valid implementation of the above?
Is this what is meant by "as if"?
Yes, if the move assignment produces no observable effect, which is the case for trivially copyable types.
I've checked a lot of move constructor/vector/noexcept threads, but I am still unsure what actually happens when things are supposed to go wrong. I can't produce an error when I expect to, so either my little test is wrong, or my understanding of the problem is wrong.
I am using a vector of a BufferTrio object, which defines a noexcept(false) move constructor, and deletes every other constructor/assignment operator so that there's nothing to fall back to:
BufferTrio(const BufferTrio&) = delete;
BufferTrio& operator=(const BufferTrio&) = delete;
BufferTrio& operator=(BufferTrio&& other) = delete;
BufferTrio(BufferTrio&& other) noexcept(false)
: vaoID(other.vaoID)
, vboID(other.vboID)
, eboID(other.eboID)
{
other.vaoID = 0;
other.vboID = 0;
other.eboID = 0;
}
Things compile and run, but from https://xinhuang.github.io/posts/2013-12-31-when-to-use-noexcept-and-when-to-not.html:
std::vector will use move when it needs to increase(or decrease) the capacity, as long as the move operation is noexcept.
Or from Optimized C++: Proven Techniques for Heightened Performance By Kurt Guntheroth:
If the move constructor and move assignment operator are not declared noexcept, std::vector uses the less efficient copy operations instead.
Since I've deleted those, my understanding is that something should be breaking here. But things are running ok with that vector. So I also created a basic loop that push_backs half a million times into a dummy vector, and then swapped that vector with another single-element dummy vector. Like so:
vector<BufferTrio> thing;
int n = 500000;
while (n--)
{
thing.push_back(BufferTrio());
}
vector<BufferTrio> thing2;
thing2.push_back(BufferTrio());
thing.swap(thing2);
cout << "Sizes are " << thing.size() << " and " << thing2.size() << endl;
cout << "Capacities are " << thing.capacity() << " and " << thing2.capacity() << endl;
Output:
Sizes are 1 and 500000
Capacities are 1 and 699913
Still no problems, so:
Should I see something going wrong, and if so, how can I demonstrate it?
A vector reallocation attempts to offer an exception guarantee, i.e. an attempt to preserve the original state if an exception is thrown during the reallocation operation. There are three scenarios:
The element type is nothrow_move_constructible: Reallocation can move elements which won't cause an exception. This is the efficient case.
The element type is CopyInsertable: if the type fails to be nothrow_move_constructible, this is sufficient to provide the strong guarantee, though copies are made during reallocation. This was the old C++03 default behaviour and is the less efficient fall-back.
The element type is neither CopyInsertable nor nothrow_move_constructible. As long as it is still move-constructible, like in your example, vector reallocation is possible, but does not provide any exception guarantees (e.g. you might lose elements if a move construction throws).
The normative wording that says this is spread out across the various reallocating functions. For example, [vector.modifiers]/push_back says:
If an exception is thrown while
inserting a single element at the end and T is CopyInsertable or is_nothrow_move_constructible_v<T> is true, there are no effects. Otherwise, if an exception is thrown by the move constructor of a
non-CopyInsertable T, the effects are unspecified.
I don't know what the authors of the posts you cite had in mind, though I can imagine that they are implicitly assuming that you want the strong exception guarantee, and so they'd like to steer you into cases (1) or (2).
There is nothing going wrong in your example. From std::vector::push_back:
If T's move constructor is not noexcept and T is not CopyInsertable into *this, vector will use the throwing move constructor. If it throws, the guarantee is waived and the effects are unspecified.
std::vector prefers non-throwing move constructors, and if none is available, will fall back on the copy constructor (throwing or not). But if that is also not available, then it has to use the throwing move constructor. Basically, the vector tries to save you from throwing constructors and leaving objects in an indeterminate state.
So in that regard, your example is correct, but if your move constructor actually threw an exception, then you'd have unspecified behavior.
TLDR So long as a type is MoveInsertable, you're okay, and here you're okay.
A type is MoveInsertable if, given the container's allocator A, an instance of the allocator assigned to variable m, a pointer to T* called p, and an r-value of type T the following expression is well-formed:
allocator_traits<A>::construct(m, p, rv);
For your std::vector<BufferTrio>, you are using the default std::allocator<BufferTrio>, so this call to construct calls
m.construct(p, std::forward<U>(rv))
Where U is a forwarding reference type (in your case it's BufferTrio&&, an rvalue reference)
So far so good,
m.construct will use placement-new to construct the member in-place([allocator.members])
::new((void *)p) U(std::forward<Args>(args)...)
At no point does this require noexcept. It's only for exception guarantee reasons.
[vector.modifiers] states that for void push_back(T&& x);
If an exception is thrown by the move
constructor of a non-CopyInsertable T, the effects are unspecified.
Finally,
Regarding your swap (Emphasis mine):
[container.requirements.general]
The expression a.swap(b), for containers a and b of a standard container type other than array, shall exchange
the values of a and b without invoking any move, copy, or swap operations on the individual container
elements.
Consider the following code.
using T = std::string;
void rotate_left(std::vector<T>& v) {
T temp = std::move(v[0]);
for (size_t i=0; i+1 < v.size(); ++i) {
v[i] = std::move(v[i+1]);
}
v.back() = std::move(temp);
}
int main()
{
std::vector<T> v(3); // a vector of three Ts
T x = std::move(v[1]); // move-from the second element
rotate_left(v);
// Can we now say that v[0] is in a moved-from state, or did we
// get undefined behavior when we moved from v[1] a second time?
}
The rotate_left function is just really simply shifting everything in the vector down by one position (and then putting the first element onto the end). My question is, does this function have defined behavior when one of the elements in the vector is in a "moved-from state"?
This is related but not quite the same as "self-move". In this case, we're moving from one moved-from object into another moved-from object, and my question is whether we can rely on this leaving both objects still in some moved-from state, or whether "assignment-from" is one of those operations that "has a precondition" and therefore can't be used on arbitrary moved-from objects.
I'm well aware that
this is perfectly fine for sane library types such as unique_ptr
this will be perfectly fine for my own user-defined types if I define them sanely
this will be problematic for my own user-defined types if I define them insanely
this is perfectly fine for all the library types I've tried on libstdc++ and libc++
So what I'm really looking for is either:
concrete wording from the Standard proving that this must be perfectly fine for all STL types going forward, or
a concrete example of an STL or Boost type "in the wild" where this code is definitely problematic
I infer from this bug that _GLIBCXX_DEBUG checks for self-move, but I can confirm that it does not check for move-from-moved; is this because they consider move-from-moved to be safe and legal, or just because nobody wrote the code to check for it yet?
Your program's behavior is well-defined, but unspecified. The difference is that v has a valid state, you just don't know what that state is without inspecting it.
This corner of the standard has been controversial, and the wording has been difficult to get right. But I believe the latest wording (C++17) is our best attempt so far, so I will quote from that (N4660).
Disclaimer, I actually linked to N4659 as N4660 is not publicly available. The difference is inconsequential.
From 20.5.5.15 Moved-from state of library types [lib.types.movedfrom]
1 Objects of types defined in the C++ standard library may be moved from (15.8). Move operations may be explicitly specified or implicitly generated. Unless otherwise specified, such moved-from objects shall be placed in a valid but unspecified state.
This paragraph is a blanket statement for all types defined by the std::lib that moved-from objects are not "poison", you just don't know value they have.
Furthermore, each algorithm (including member functions) defined in the standard may have a list of preconditions that must be true prior to calling that algorithm. If there are no preconditions listed, that means that you can always call that function.
Move assignment for all types defined in the std::lib never have any preconditions listed for the left or right hand side arguments.
It is hard to quote something that doesn't exist, but that is the way this specification works.
Generalizing further, the following section refers to all types (i.e. user-supplied) that are used with the std::lib:
20.5.3.1 Template argument requirements [utility.arg.requirements]
Table 23 — MoveConstructible requirements [moveconstructible]
T u = rv;
T(rv);
rv's state is unspecified in the post-condition.
Table 25 — MoveAssignable requirements [moveassignable]
t = rv
Only if t and rv do not refer to the same object, t is equivalent to the value of rv before the assignment.
Afterwards, rv's state is unspecified (whether or not t and rv refer to the same object).
Furthermore this sections notes (notes are non-normative and often the normative text appears elsewhere) that rv must still meet the requirements of the library component (algorithm) it is being used with, even though it has been moved from.
For example, std::sort is allowed to move from a value x, and then use that x in a comparison expression. x must be LessThanComparable, whether or not x is moved-from. Only x's value is unspecified.
bool b = x < x; // b must be false, no matter what!
Does the standard define precisely what I can do with an object once it has been moved from? I used to think that all you can do with a moved-from object is do destruct it, but that would not be sufficient.
For example, take the function template swap as defined in the standard library:
template <typename T>
void swap(T& a, T& b)
{
T c = std::move(a); // line 1
a = std::move(b); // line 2: assignment to moved-from object!
b = std::move(c); // line 3: assignment to moved-from object!
}
Obviously, it must be possible to assign to moved-from objects, otherwise lines 2 and 3 would fail. So what else can I do with moved-from objects? Where exactly can I find these details in the standard?
(By the way, why is it T c = std::move(a); instead of T c(std::move(a)); in line 1?)
17.6.5.15 [lib.types.movedfrom]
Objects of types defined in the C++ standard library may be moved from
(12.8). Move operations may be explicitly specified or implicitly
generated. Unless otherwise specified, such moved-from objects shall
be placed in a valid but unspecified state.
When an object is in an unspecified state, you can perform any operation on the object which has no preconditions. If there is an operation with preconditions you wish to perform, you can not directly perform that operation because you do not know if the unspecified-state of the object satisfies the preconditions.
Examples of operations that generally do not have preconditions:
destruction
assignment
const observers such as get, empty, size
Examples of operations that generally do have preconditions:
dereference
pop_back
This answer now appears in video format here: http://www.youtube.com/watch?v=vLinb2fgkHk&t=47m10s
Moved-from objects exist in an unspecified, but valid, state. That suggests that whilst the object might not be capable of doing much anymore, all of its member functions should still exhibit defined behaviour — including operator= — and all its members in a defined state- and it still requires destruction. The Standard gives no specific definitions because it would be unique to each UDT, but you might be able to find specifications for Standard types. Some like containers are relatively obvious — they just move their contents around and an empty container is a well-defined valid state. Primitives don't modify the moved-from object.
Side note: I believe it's T c = std::move(a) so that if the move constructor (or copy constructor if no move is provided) is explicit the function will fail.
What happens if to a movable object if I call
std::set<>::insert on it, and the insertion doesn't take place
because there is already an object with the same key present in
the set. In particular, is the following valid:
struct Object
{
std::string key;
// ...
struct OrderByKey
{
bool operator()( Object const& lhs, Object const& rhs) const
{
return lhs.key < rhs.key;
}
};
Object( Object&& other )
: key(std::move(other.key))
// ...
{
}
};
and:
std::set<Object, Object::OrderByKey> registry;
void
register(Object&& o)
{
auto status = registry.insert(std::move(o));
if (!status.second)
throw std::runtime_error("Duplicate entry for " + o.key);
}
After the registry.insert, when no insertion takes place, has
o been moved or not. (If it might have been moved, I need to
save a copy of the string before hand, in order to use it in the
error message. (And yes, I know that I can always write:
throw std::runtime_error( "Duplicate entry for " + status->first.key );
Which is what I'll probably do. But I would still like to know
what the standard says about this.)
A std::move()ed object passed to a standard library function should be considered to be moved from: the library is free to consider the object to be its only reference, i.e., it may move from it even if it doesn't use it. The relevant clause is 17.6.4.9 [res.on.arguments] paragraph 2, third bullet (it seems identical in C++11 and C++14):
If a function argument binds to an rvalue reference, the implementation may assume that this parameter is a unique reference to this argument. ...
This is (very simmilar to) LWG Active issue 2362 which deals with emplace. IIRC the sentiment is that it should be guaranteed that the object is only moved iff it is inserted/emplaced. With emplace it seems to be not trivial to achieve. I do not remember if the situation is easier for insert.
The third bullet of [res.on.arguments]/1 from N3936:
If a function argument binds to an rvalue reference parameter, the implementation may assume that
this parameter is a unique reference to this argument. [ Note: If the parameter is a generic parameter of the form T&& and an lvalue of type A is bound, the argument binds to an lvalue reference (14.8.2.1) and thus is not covered by the previous sentence. —end note ] [ Note: If a program casts an lvalue to an xvalue while passing that lvalue to a library function (e.g. by calling the function with the argument move(x)), the program is effectively asking that function to treat that lvalue as a temporary. The implementation is free to optimize away aliasing checks which might be needed if the argument was an lvalue. —end note ]
despite on its face being about aliasing, can be interpreted as allowing the implementation to do virtually anything to an argument that is passed to a standard library function bound to an rvalue reference. As Fabio says, there are situations in which tightening this specification could be useful, and the committee is looking at some of them.
Well, to answer your last question
I would still like to know what the standard says about this
the standard specifies what happen with insert(t) in Table 102 - Associative container requirements, p717 of N3376 (emphasis mine):
Requires: If t is a non-const rvalue expression, value_type shall be MoveInsertable into X; otherwise, value_type shall be CopyInsertable into X.
Effects: Inserts t if and only if there is no element in the container with key equivalent to the key of t. The bool component of the returned pair is true if and only if the insertion takes place, and the iterator component of the pair points to the element with key equivalent to the key of t.
so I'd say that nothing should happen and that your Object is still valid and not unspecified state.
EDIT: As someone pointed out in the comment, this may actually depends on the implementation as it requires explicitly that the set is not modified, but states no explicit requirement on the argument and whether it's been moved or not.