uninitialized_copy
appears to not be able to meet its exception-safety guaranteeSection: 26.11.5 [uninitialized.copy] Status: C++20 Submitter: Jon Cohen Opened: 2018-01-24 Last modified: 2021-02-25
Priority: 2
View all other issues in [uninitialized.copy].
View all issues with C++20 status.
Discussion:
I believe that uninitialized_copy
is unable to meet its exception-safety guarantee in the
presence of throwing move constructors:
uninitialized_copy
:
the provided iterators satisfy the InputIterator
requirements (24.3.5.3 [input.iterators])
if an exception is thrown during the algorithm then there are no effects
Iter
. Then std::move_iterator<Iter>
appears
to also be an input iterator. Notably, it still satisfies that (void)*a, *a
is equivalent to
*a
for move iterator a
since the dereference only forms an rvalue reference, it
doesn't actually perform the move operation (24.3.5.3 [input.iterators] Table 95 — "Input iterator requirements").
Suppose also that we have a type T
whose move constructor can throw, a range of T
's
[tbegin, tend)
, and a pointer to an uninitialized buffer of T
's
buf
. Then std::uninitialized_copy(std::make_move_iterator(tbegin),
std::make_move_iterator(tend), buf)
can't possibly satisfy the property that it has
no effects if one of the moves throws — we'll have a T
left in a moved-from state with
no way of recovering.
See here for an example in code.
It seems like the correct specification for uninitialized_copy
should be that if
InputIterator
's operator*
returns an rvalue reference and
InputIterator::value_type
's move constructor is not marked noexcept
, then
uninitialized_copy
will leave the objects in the underlying range in a valid but
unspecified state.
[2018-01-24, Casey comments and provides wording]
This issue points out a particular hole in the "..if an exception is thrown in the following algorithms
there are no effects." wording for the "uninitialized" memory algorithms
(26.11 [specialized.algorithms]/1) and suggests a PR to patch over said hole. The true problem
here is that "no effects" is not and never has been implementable. For example, "first != last
"
may have observable effects that an implementation is required to somehow reverse if some later operation
throws an exception.
[2018-02-05, Priority set to 2 after mailing list discussion]
[2018-06 Rapperswil Thursday issues processing]
Status to Ready
[2018-11, Adopted in San Diego]
Proposed resolution:
This wording is relative to N4713.
Modify 26.11 [specialized.algorithms] as indicated:
-1- […]
Unless otherwise specified, if an exception is thrown in the following algorithms objects constructed by a placement new-expression (7.6.2.8 [expr.new]) are destroyed in an unspecified order before allowing the exception to propagatethere are no effects.
Modify 26.11.6 [uninitialized.move] as indicated (The removed paragraphs are now unnecessary):
template<class InputIterator, class ForwardIterator> ForwardIterator uninitialized_move(InputIterator first, InputIterator last, ForwardIterator result);[…]
-2- Remarks: If an exception is thrown, some objects in the range[first, last)
are left in a valid but unspecified state.template<class InputIterator, class Size, class ForwardIterator> pair<InputIterator, ForwardIterator> uninitialized_move_n(InputIterator first, Size n, ForwardIterator result);[…]
-4- Remarks: If an exception is thrown, some objects in the range[first, std::next(first, n))
are left in a valid but unspecified state.