Is a moved-from vector always empty? - c++

I know that generally the standard places few requirements on the values which have been moved from:
N3485 17.6.5.15 [lib.types.movedfrom]/1:
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.
I can't find anything about vector that explicitly excludes it from this paragraph. However, I can't come up with a sane implementation that would result in the vector being not empty.
Is there some standardese that entails this that I'm missing or is this similar to treating basic_string as a contiguous buffer in C++03?

I'm coming to this party late, and offering an additional answer because I do not believe any other answer at this time is completely correct.
Question:
Is a moved-from vector always empty?
Answer:
Usually, but no, not always.
The gory details:
vector has no standard-defined moved-from state like some types do (e.g. unique_ptr is specified to be equal to nullptr after being moved from). However the requirements for vector are such that there are not too many options.
The answer depends on whether we're talking about vector's move constructor or move assignment operator. In the latter case, the answer also depends on the vector's allocator.
vector<T, A>::vector(vector&& v)
This operation must have constant complexity. That means that there are no options but to steal resources from v to construct *this, leaving v in an empty state. This is true no matter what the allocator A is, nor what the type T is.
So for the move constructor, yes, the moved-from vector will always be empty. This is not directly specified, but falls out of the complexity requirement, and the fact that there is no other way to implement it.
vector<T, A>&
vector<T, A>::operator=(vector&& v)
This is considerably more complicated. There are 3 major cases:
One:
allocator_traits<A>::propagate_on_container_move_assignment::value == true
(propagate_on_container_move_assignment evaluates to true_type)
In this case the move assignment operator will destruct all elements in *this, deallocate capacity using the allocator from *this, move assign the allocators, and then transfer ownership of the memory buffer from v to *this. Except for the destruction of elements in *this, this is an O(1) complexity operation. And typically (e.g. in most but not all std::algorithms), the lhs of a move assignment has empty() == true prior to the move assignment.
Note: In C++11 the propagate_on_container_move_assignment for std::allocator is false_type, but this has been changed to true_type for C++1y (y == 4 we hope).
In case One, the moved-from vector will always be empty.
Two:
allocator_traits<A>::propagate_on_container_move_assignment::value == false
&& get_allocator() == v.get_allocator()
(propagate_on_container_move_assignment evaluates to false_type, and the two allocators compare equal)
In this case, the move assignment operator behaves just like case One, with the following exceptions:
The allocators are not move assigned.
The decision between this case and case Three happens at run time, and case Three requires more of T, and thus so does case Two, even though case Two doesn't actually execute those extra requirements on T.
In case Two, the moved-from vector will always be empty.
Three:
allocator_traits<A>::propagate_on_container_move_assignment::value == false
&& get_allocator() != v.get_allocator()
(propagate_on_container_move_assignment evaluates to false_type, and the two allocators do not compare equal)
In this case the implementation can not move assign the allocators, nor can it transfer any resources from v to *this (resources being the memory buffer). In this case, the only way to implement the move assignment operator is to effectively:
typedef move_iterator<iterator> Ip;
assign(Ip(v.begin()), Ip(v.end()));
That is, move each individual T from v to *this. The assign can reuse both capacity and size in *this if available. For example if *this has the same size as v the implementation can move assign each T from v to *this. This requires T to be MoveAssignable. Note that MoveAssignable does not require T to have a move assignment operator. A copy assignment operator will also suffice. MoveAssignable just means T has to be assignable from an rvalue T.
If the size of *this is not sufficient, then new T will have to be constructed in *this. This requires T to be MoveInsertable. For any sane allocator I can think of, MoveInsertable boils down to the same thing as MoveConstructible, which means constructible from an rvalue T (does not imply the existence of a move constructor for T).
In case Three, the moved-from vector will in general not be empty. It could be full of moved-from elements. If the elements don't have a move constructor, this could be equivalent to a copy assignment. However, there is nothing that mandates this. The implementor is free to do some extra work and execute v.clear() if he so desires, leaving v empty. I am not aware of any implementation doing so, nor am I aware of any motivation for an implementation to do so. But I don't see anything forbidding it.
David Rodríguez reports that GCC 4.8.1 calls v.clear() in this case, leaving v empty. libc++ does not, leaving v not empty. Both implementations are conforming.

While it might not be a sane implementation in the general case, a valid implementation of the move constructor/assignment is just copying the data from the source, leaving the source untouched. Additionally, for the case of assignment, move can be implemented as swap, and the moved-from container might contain the old value of the moved-to container.
Implementing move as copy can actually happen if you use polymorphic allocators, as we do, and the allocator is not deemed to be part of the value of the object (and thus, assignment never changes the actual allocator being used). In this context, a move operation can detect whether both the source and the destination use the same allocator. If they use the same allocator the move operation can just move the data from the source. If they use different allocators then the destination must copy the source container.

In a lot of situations, move-construction and move-assignment can be implemented by delegating to swap - especially if no allocators are involved. There are several reasons for doing that:
swap has to be implemented anyway
developer efficiency because less code has to be written
runtime efficiency because fewer operations are executed in total
Here is an example for move-assignment. In this case, the move-from vector will not be empty, if the moved-to vector was not empty.
auto operator=(vector&& rhs) -> vector&
{
if (/* allocator is neither move- nor swap-aware */) {
swap(rhs);
} else {
...
}
return *this;
}

I left comments to this effect on other answers, but had to rush off before fully explaining. The result of a moved-from vector must always be empty, or in the case of move assignment, must be either empty or the previous object's state (i.e. a swap), because otherwise the iterator invalidation rules cannot be met, namely that a move does not invalidate them. Consider:
std::vector<int> move;
std::vector<int>::iterator it;
{
std::vector<int> x(some_size);
it = x.begin();
move = std::move(x);
}
std::cout << *it;
Here you can see that iterator invalidation does expose the implementation of the move. The requirement for this code to be legal, specifically that the iterator remains valid, prevents the implementation from performing a copy, or small-object-storage or any similar thing. If a copy was made, then it would be invalidated when the optional is emptied, and the same is true if the vector uses some kind of SSO-based storage. Essentially, the only reasonable possible implementation is to swap pointers, or simply move them.
Kindly view the Standard quotes on requirements for all containers:
X u(rv)
X u = rv
post: u shall be equal to the value that rv had before this construction
a = rv
a shall be equal to the value that rv had before this assignment
Iterator validity is part of the value of a container. Although the Standard does not unambiguously state this directly, we can see in, for example,
begin() returns an iterator referring to the first element in the
container. end() returns an iterator which is the past-the-end value
for the container. If the container is empty, then begin() == end();
Any implementation which actually did move from the elements of the source instead of swapping the memory would be defective, so I suggest that any Standard wordings saying otherwise is a defect- not least of which because the Standard is not in fact very clear on this point. These quotes are from N3691.

Related

Using an object without copy and without a noexcept move constructor in a vector. What actually breaks and how can I confirm it?

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.

Is a runtime equality check of allocators bound to happen even if `is_always_equal` is true?

When implementing an allocator aware container, some members require special handling with respect to the allocators.
For example (as I understand it): If the container is move assigned and
propagate_on_container_move_assignment^::value
is false, memory can still be "hijacked" iff both allocator compare equal.
The question is now, whether the equality check is actually required (or should be performed) if
is_always_equal::value
is true or not?
Rationale:
The standard library I'm using supports the is_always_equal trait property.
The move assignment operator of the std::vector implementation has the noexcept specification
noexcept(_alt::propagate_on_container_move_assignment::value
|| _alt::is_always_equal::value)
and effectively has a call like
_move_it(std::forward<...>(rhs),
_alt::propagate_on_container_move_assignment{});
(with _alt being the allocator traits) where the move_it overload in case of non-propagating allocators looks like:
void _move_it(...&& rhs, std::false_type)
{
if(_alloc() == rhs._alloc())
// steal
else
// move data elements
}
with the true_type overload just stealing the memory.
Thus, we have a compile-time noexcept guarantee in case is_always_true::value is true.
The noexcept could then be violated in two ways:
_alloc() == rhs._alloc() throws.
_alloc() != rhs._alloc() returns false and an element move constructor throws.
(Note: I'm aware of the fact that a throwing operator== as well as a non-equality in context of a is_always_true promise both are violating standard requirements.)
Changing the above call to
_move_it(std::forward<...>(rhs),
std::disjunction<_alt::propagate_on_container_move_assignment,
_alt::is_always_equal>{});
however, should enforce noexcept because stealing/swapping the pointers actually can't throw and it would avoid a runtime-equality check (that might or might not be optimized out in case of stateless allocators).
Is there a specific reason to actually perform the equality check rather than circumvent it?

std::vector::erase exception safety

I have read that std::vector erase method use move operations only if type is known to not emit exceptions due to strong exception safety. Other comments are that erase method guarantee basic or no throw exception safety depending on that if element constructor throws or not. I wasn't able to clarify that in my C++11 draft. I did test and it shows basic exception safety guarantee, it also used move constructor which was not marked as noexcept. Did I overlook something ? What is right ?
Table 100 -- Sequence container requirements in section 23.2.3 [sequence.reqmts] says:
a.erase(q)
Requires: For vector and deque, T shall be MoveAssignable.
This means that the implementation can call no operations on T except to destruct it or move assign it. Note that if the implementation move assigns T that doesn't guarantee that a move assignment operator will be called. For example T may not have a move assignment operator, and so in that case a copy assignment operator could be called. However the implementation is not allowed to copy assign the T, only move assign it.
*i = std::move(*j); // implementation can do this
*i = *j; // implementation can not do this
Furthermore 23.3.6.5 vector modifiers [vector.modifiers] says the following:
iterator erase(const_iterator position);
iterator erase(const_iterator first, const_iterator last);
Throws: Nothing unless an exception is thrown by the copy constructor, move constructor, assignment operator, or move assignment
operator of T.
I must admit I sighed when I read this. There is clearly a minor defect here. This operation is not allowed to form any expressions that would directly construct a T. Perhaps one is constructed as an implementation detail inside an assignment operator of T, but that is of no concern to this specification. The concern is does this expression throw or not:
*i = std::move(*j); // implementation can do this. Will it throw?
If that expression (where i and j are iterators referring to a T) does not throw, then vector::erase has the no-throw guarantee. Otherwise vector::erase has the basic exception safety guarantee.
Note that for this operation, the implementation is not allowed to fall back to copy assignment if is_nothrow_move_assignable<T>::value is false. Such logic is present in other vector operations such as push_back, but not here.
Also note the Complexity specification of this same section:
Complexity: The destructor of T is called the number of times equal to the number of the elements erased, but the move assignment
operator of T is called the number of times equal to the number of
elements in the vector after the erased elements.
Restated: If you erase a range of elements that end with the end of the vector, zero move assignments will be performed, and move assignment is the only thing that might throw. So you get back the no-throw guarantee if you are erasing at the end, even if is_nothrow_move_assignable<T>::value is false.
23.3.6.5 Throws: Nothing unless an exception is thrown by the copy constructor, move constructor, assignment operator, or move assignment operator of T.
Provided your implementation conforms to this, it may implement the erase as it sees fit. There is no implicit exception safety guarantee as far as I can tell.

Capacity of the vector from which data was moved [duplicate]

This question already has answers here:
Is a moved-from vector always empty?
(4 answers)
Closed 4 years ago.
Is it mandatory, that the capacity of the std::vector is zero, after moving data from it? Assume that the memory allocators of source and destination vectors are always matching.
std::vector< int > v{1, 2, 3};
assert(0 < v.capacity());
std::vector< int > w;
w = std::move(v);
assert(0 == v.capacity());
Here said that move assignment operator leaves stealed RHS vector in a valid, but unspecified state. But nowhere pointed, that vector should perform additional memory allocations during move assignment operation. Another note is: vector have continuous memory region as underlying storage.
If your question had been about move construction of a vector, the answer would be easy, the source vector is left empty after the move. This is because of the requirement in
Table 99 — Allocator-aware container requirements
Expression:
X(rv)
X u(rv)
Requires: move construction of A shall not exit via an exception.
post: u shall have the same elements as rv had before this construction; the value of u.get_allocator() shall be the same as the value of rv.get_allocator() before this construction.
Complexity: constant
(the A in the requirements clause is the allocator type)
The constant complexity leaves no option but to steal resources from the source vector, which means for it to be in a valid, but unspecified state you'd need to leave it empty, and capacity() will equal zero.
The answer is considerably more complicated in case of a move assignment. The same Table 99 lists the requirement for move assignment as
Expression:
a = rv
Return type:
X&
Requires: If allocator_traits<allocator_type>::propagate_on_container_move_assignment::value is
false, T is MoveInsertable into X and MoveAssignable. All existing elements of a are either move assigned to or destroyed.
post: a shall be equal to the value that rv had before this assignment.
Complexity: linear
There are different cases to evaluate here.
First, say allocator_traits<allocator_type>::propagate_on_container_move_assignment::value == true, then the allocator can also be move assigned. This is mentioned in §23.2.1/8
... The allocator may be replaced only via assignment or swap(). Allocator replacement is performed by copy assignment, move assignment, or swapping of the allocator
only if allocator_traits<allocator_type>::propagate_on_container_copy_assignment::value,
allocator_traits<allocator_type>::propagate_on_container_move_assignment::value, or allocator_traits<allocator_type>::propagate_on_container_swap::value is true within the implementation of the corresponding container operation.
So the destination vector will destroy its elements, the allocator from the source is moved and the destination vector takes ownership of the memory buffer from the source. This will leave the source vector empty, and capacity() will equal zero.
Now let's consider the case where allocator_traits<allocator_type>::propagate_on_container_move_assignment::value == false. This means the allocator from the source cannot be move assigned to the destination vector. So you need to check the two allocators for equality before determining what to do.
If dest.get_allocator() == src.get_allocator(), then the destination vector is free to take ownership of the memory buffer from the source because it can use its own allocator to deallocate the storage.
Table 28 — Allocator requirements
Expression:
a1 == a2
Return type:
bool
returns true only if storage allocated from each can be deallocated via the other. ...
The sequence of operations performed is the same as the first case, except the source allocator is not move assigned. This will leave the source vector empty, and capacity() will equal zero.
In the last case, if allocator_traits<allocator_type>::propagate_on_container_move_assignment::value == false and dest.get_allocator() != src.get_allocator(), then the source allocator cannot be moved, and the destination allocator is unable to deallocate the storage allocated by the source allocator, so it cannot steal the memory buffer from source.
Each element from the source vector must be either move inserted or move assigned to the destination vector. Which operation gets done depends on the existing size and capacity of the destination vector.
The source vector retains ownership of its memory buffer after the move assignment, and it is up to the implementation to decide whether to deallocate the buffer or not, and the vector will most likely have capacity() greater than 0.
To ensure you do not run into undefined behavior when trying to resuse a vector that has been move assigned from, you should first call the clear() member function. This can be safely done since vector::clear has no pre-conditions, and will return the vector to a valid and specified state.
Also, vector::capacity has no pre-conditions either, so you can always query the capacity() of a moved from vector.
The state of the moved-from vector is unspecified but valid after the move, as you found out.
This means that it could really be in any valid state, in particular you can't assume that its capacity will be 0. It will probably be zero, and that would make a lot of sense, but that's not at all guaranteed.
But again, in practice if you don't care all that much about the standard, I suppose that you could rely on the capacity being 0. Due to the constraints on move operations, the vector move constructor pretty much has to steal memory from the moved-from one, leaving it empty. That's what will happen in almost all cases/implementations, but that's just not required.
A particularly twisted implementation could decide to reserve some elements in the moved-from vector just to mess with you. That technically wouldn't break any requirement.

Is it safe to push_back an element from the same vector?

vector<int> v;
v.push_back(1);
v.push_back(v[0]);
If the second push_back causes a reallocation, the reference to the first integer in the vector will no longer be valid. So this isn't safe?
vector<int> v;
v.push_back(1);
v.reserve(v.size() + 1);
v.push_back(v[0]);
This makes it safe?
It looks like http://www.open-std.org/jtc1/sc22/wg21/docs/lwg-closed.html#526 addressed this problem (or something very similar to it) as a potential defect in the standard:
1) Parameters taken by const reference can be changed during execution
of the function
Examples:
Given std::vector v:
v.insert(v.begin(), v[2]);
v[2] can be changed by moving elements of vector
The proposed resolution was that this was not a defect:
vector::insert(iter, value) is required to work because the standard
doesn't give permission for it not to work.
Yes, it's safe, and standard library implementations jump through hoops to make it so.
I believe implementers trace this requirement back to 23.2/11 somehow, but I can't figure out how, and I can't find something more concrete either. The best I can find is this article:
http://www.drdobbs.com/cpp/copying-container-elements-from-the-c-li/240155771
Inspection of libc++'s and libstdc++'s implementations shows that they are also safe.
The standard guarantees even your first example to be safe. Quoting C++11
[sequence.reqmts]
3 In Tables 100 and 101 ... X denotes a sequence container class, a denotes a value of X containing elements of type T, ... t denotes an lvalue or a const rvalue of X::value_type
16 Table 101 ...
Expression a.push_back(t) Return type void Operational semantics Appends a copy of t. Requires: T shall be CopyInsertable into X. Container basic_string, deque, list, vector
So even though it's not exactly trivial, the implementation must guarantee it will not invalidate the reference when doing the push_back.
It is not obvious that the first example is safe, because the simplest implementation of push_back would be to first reallocate the vector, if needed, and then copy the reference.
But at least it seems to be safe with Visual Studio 2010. Its implementation of push_back does special handling of the case when you push back an element in the vector.
The code is structured as follows:
void push_back(const _Ty& _Val)
{ // insert element at end
if (_Inside(_STD addressof(_Val)))
{ // push back an element
...
}
else
{ // push back a non-element
...
}
}
This isn't a guarantee from the standard, but as another data point, v.push_back(v[0]) is safe for LLVM's libc++.
libc++'s std::vector::push_back calls __push_back_slow_path when it needs to reallocate memory:
void __push_back_slow_path(_Up& __x) {
allocator_type& __a = this->__alloc();
__split_buffer<value_type, allocator_type&> __v(__recommend(size() + 1),
size(),
__a);
// Note that we construct a copy of __x before deallocating
// the existing storage or moving existing elements.
__alloc_traits::construct(__a,
_VSTD::__to_raw_pointer(__v.__end_),
_VSTD::forward<_Up>(__x));
__v.__end_++;
// Moving existing elements happens here:
__swap_out_circular_buffer(__v);
// When __v goes out of scope, __x will be invalid.
}
The first version is definitely NOT safe:
Operations on iterators obtained by calling a standard library container or string member function may access the underlying container, but shall not modify it. [ Note: In particular, container operations that invalidate iterators conflict with operations on iterators associated with that container. — end note ]
from section 17.6.5.9
Note that this is the section on data races, which people normally think of in conjunction with threading... but the actual definition involves "happens before" relationships, and I don't see any ordering relationship between the multiple side-effects of push_back in play here, namely the reference invalidation seems not to be defined as ordered with respect to copy-constructing the new tail element.
It is completely safe.
In your second example you have
v.reserve(v.size() + 1);
which is not needed because if vector goes out of its size, it will imply the reserve.
Vector is responsible for this stuff, not you.
Both are safe since push_back will copy the value, not the reference. If you are storing pointers, that is still safe as far as the vector is concerned, but just know that you'll have two elements of your vector pointing to the same data.
Section 23.2.1 General Container Requirements
16
a.push_back(t) Appends a copy of t. Requires: T shall be CopyInsertable into X.
a.push_back(rv) Appends a copy of rv. Requires: T shall be MoveInsertable into X.
Implementations of push_back must therefore ensure that a copy of v[0] is inserted. By counter example, assuming an implementation that would reallocate before copying, it would not assuredly append a copy of v[0] and as such violate the specs.
From 23.3.6.5/1: Causes reallocation if the new size is greater than the old capacity. If no reallocation happens, all the iterators and references before the insertion point remain valid.
Since we're inserting at the end, no references will be invalidated if the vector isn't resized. So if the vector's capacity() > size() then it's guaranteed to work, otherwise it's guaranteed to be undefined behavior.