This page is a snapshot from the LWG issues list, see the Library Active Issues List for more information and the meaning of Voting status.

3918. std::uninitialized_move/_n and guaranteed copy elision

Section: 26.11.6 [uninitialized.move] Status: Voting Submitter: Jiang An Opened: 2023-04-04 Last modified: 2024-11-19

Priority: 3

View all issues with Voting status.

Discussion:

Currently std::move is unconditionally used in std::uninitialized_move and std::uninitialized_move_n, which may involve unnecessary move construction if dereferencing the input iterator yields a prvalue.

The status quo was mentioned in paper issue #975, but no further process is done since then.

[2023-06-01; Reflector poll]

Set priority to 3 after reflector poll. Send to LEWG.

"P2283 wants to remove guaranteed elision here." "Poorly motivated, not clear anybody is using these algos with proxy iterators." "Consider using iter_move in the move algos."

Previous resolution [SUPERSEDED]:

This wording is relative to N4944.

  1. Modify 26.11.1 [specialized.algorithms.general] as indicated:

    -3- Some algorithms specified in 26.11 [specialized.algorithms] make use of the following exposition-only functions voidify:

    template<class T>
      constexpr void* voidify(T& obj) noexcept {
        return addressof(obj);
      }
      
    template<class I>
      decltype(auto) deref-move(const I& it) {
        if constexpr (is_lvalue_reference_v<decltype(*it)>)
          return std::move(*it);
        else
          return *it;
      }
    
  2. Modify 26.11.6 [uninitialized.move] as indicated:

    template<class InputIterator, class NoThrowForwardIterator>
      NoThrowForwardIterator uninitialized_move(InputIterator first, InputIterator last,
                                                NoThrowForwardIterator result);
    

    -1- Preconditions: result + [0, (last - first)) does not overlap with [first, last).

    -2- Effects: Equivalent to:

    for (; first != last; (void)++result, ++first)
      ::new (voidify(*result))
        typename iterator_traits<NoThrowForwardIterator>::value_type(std::move(*deref-move(first));
    return result;
    
    […]
    template<class InputIterator, class Size, class NoThrowForwardIterator>
      pair<InputIterator, NoThrowForwardIterator>
        uninitialized_move_n(InputIterator first, Size n, NoThrowForwardIterator result);
    

    -6- Preconditions: result + [0, n) does not overlap with first + [0, n).

    -7- Effects: Equivalent to:

    for (; n > 0; ++result,(void) ++first, --n)
      ::new (voidify(*result))
        typename iterator_traits<NoThrowForwardIterator>::value_type(std::move(*deref-move(first));
    return {first, result};
    

[2024-03-22; Tokyo: Jonathan updates wording after LEWG review]

LEWG agrees it would be good to do this. Using iter_move was discussed, but it was noted that the versions of these algos in the ranges namespace already use it and introducing ranges::iter_move into the non-ranges versions wasn't desirable. It was observed that the proposed deref-move has a const I& parameter which would be ill-formed for any iterator with a non-const operator* member. Suggested removing the const and recommended LWG to accept the proposed resolution.

Previous resolution [SUPERSEDED]:

This wording is relative to N4971.

  1. Modify 26.11.1 [specialized.algorithms.general] as indicated:

    -3- Some algorithms specified in 26.11 [specialized.algorithms] make use of the following exposition-only functions voidify:

    template<class T>
      constexpr void* voidify(T& obj) noexcept {
        return addressof(obj);
      }
      
    template<class I>
      decltype(auto) deref-move(I& it) {
        if constexpr (is_lvalue_reference_v<decltype(*it)>)
          return std::move(*it);
        else
          return *it;
      }
    
  2. Modify 26.11.6 [uninitialized.move] as indicated:

    template<class InputIterator, class NoThrowForwardIterator>
      NoThrowForwardIterator uninitialized_move(InputIterator first, InputIterator last,
                                                NoThrowForwardIterator result);
    

    -1- Preconditions: result + [0, (last - first)) does not overlap with [first, last).

    -2- Effects: Equivalent to:

    for (; first != last; (void)++result, ++first)
      ::new (voidify(*result))
        typename iterator_traits<NoThrowForwardIterator>::value_type(std::move(*deref-move(first));
    return result;
    
    […]
    template<class InputIterator, class Size, class NoThrowForwardIterator>
      pair<InputIterator, NoThrowForwardIterator>
        uninitialized_move_n(InputIterator first, Size n, NoThrowForwardIterator result);
    

    -6- Preconditions: result + [0, n) does not overlap with first + [0, n).

    -7- Effects: Equivalent to:

    for (; n > 0; ++result,(void) ++first, --n)
      ::new (voidify(*result))
        typename iterator_traits<NoThrowForwardIterator>::value_type(std::move(*deref-move(first));
    return {first, result};
    

[St. Louis 2024-06-24; revert P/R and move to Ready]

Tim observed that the iterator requirements require all iterators to be const-dereferenceable, so there was no reason to remove the const. Restore the original resolution and move to Ready.

Proposed resolution:

This wording is relative to N4971.

  1. Modify 26.11.1 [specialized.algorithms.general] as indicated:

    -3- Some algorithms specified in 26.11 [specialized.algorithms] make use of the following exposition-only functions voidify:

    template<class T>
      constexpr void* voidify(T& obj) noexcept {
        return addressof(obj);
      }
    
    template<class I>
      decltype(auto) deref-move(I& it) {
        if constexpr (is_lvalue_reference_v<decltype(*it)>)
          return std::move(*it);
        else
          return *it;
      }
    
  2. Modify 26.11.6 [uninitialized.move] as indicated:

    template<class InputIterator, class NoThrowForwardIterator>
      NoThrowForwardIterator uninitialized_move(InputIterator first, InputIterator last,
                                                NoThrowForwardIterator result);
    

    -1- Preconditions: result + [0, (last - first)) does not overlap with [first, last).

    -2- Effects: Equivalent to:

    for (; first != last; (void)++result, ++first)
      ::new (voidify(*result))
        typename iterator_traits<NoThrowForwardIterator>::value_type(std::move(*deref-move(first));
    return result;
    
    […]
    template<class InputIterator, class Size, class NoThrowForwardIterator>
      pair<InputIterator, NoThrowForwardIterator>
        uninitialized_move_n(InputIterator first, Size n, NoThrowForwardIterator result);
    

    -6- Preconditions: result + [0, n) does not overlap with first + [0, n).

    -7- Effects: Equivalent to:

    for (; n > 0; ++result,(void) ++first, --n)
      ::new (voidify(*result))
        typename iterator_traits<NoThrowForwardIterator>::value_type(std::move(*deref-move(first));
    return {first, result};