Search code examples
c++iteratorinitializationdereference

Why is it not undefined behavior that `std::uninitialized_copy` typically dereferences an iterator to uninitialized memory?


I'm aware that dereferencing a pointer or iterator that points to uninitialized memory is illegal, unless it's a special iterator such as std::raw_storage_iterator.

It then seems strange to me that the std::uninitialized_ family of algorithms seem to do this? E.g. The Equivalent behaviour for std::uninitialized_copy under § 23.10.10.4 of the C++17 standard is stated as this:

template <class InputIterator, class ForwardIterator> ForwardIterator uninitialized_copy(InputIterator first, InputIterator last, ForwardIterator result);

Effects: As if by:

for (; first != last; ++d_first, (void) ++first) ::new (static_cast<void*>(addressof(*result))) typename iterator_traits<ForwardIt>::value_type(*first);

Where result is a ForwardIterator to a range of uninitialized memory. Similarly, en.cppreference's example and GCC 7.5 (line 83) do this. I must be missing something; why is this legal? I'm specifically referring to:

static_cast<void*>(addressof(*result))


Solution

  • I'm aware that dereferencing a pointer or iterator that points to uninitialized memory is illegal

    Not quite. The indirection alone is not illegal. Behaviour is only undefined in case of performing operations such as those that depend on the value.

    std::addressof does not access the value of referred object. It only takes its address. This is something that is allowed on objects before and after their lifetime while their storage has been allocated.

    Even if this wasn't true due to some technicality in the rules, standard library implementation is not necessarily limited by the rules of the language.


    Standard quotes (latest draft):

    [basic.life]

    Before the lifetime of an object has started but after the storage which the object will occupy has been allocated ... any pointer that represents the address of the storage location where the object will be ... located may be used but only in limited ways. For an object under construction or destruction, see [class.cdtor]. Otherwise, such a pointer refers to allocated storage ([basic.stc.dynamic.allocation]), and using the pointer as if the pointer were of type void* is well-defined. Indirection through such a pointer is permitted but the resulting lvalue may only be used in limited ways, as described below. The program has undefined behavior if: (no cases that apply here)

    Similarly, before the lifetime of an object has started but after the storage which the object will occupy has been allocated ... any glvalue that refers to the original object may be used but only in limited ways. For an object under construction or destruction, see [class.cdtor]. Otherwise, such a glvalue refers to allocated storage ([basic.stc.dynamic.allocation]), and using the properties of the glvalue that do not depend on its value is well-defined. The program has undefined behavior if: (no cases that apply here)