LWG 2511: guaranteed copy elision for piecewise construction (rev. 1)

Revision History

Changes since P0475R0:

Replace UNPACK pseudo-function with conversion to tuple of references.

Discussion

The 2511 issue ("scoped_allocator_adaptor piecewise construction
does not require CopyConstructible") suggests removing the
CopyConstructible requirement from scoped_allocator_adaptor's piecewise
construction function. I don't think I forgot to remove that requirement in my
resolution for 2203, because the new post-2203 wording still
requires copies (or moves) in some cases. However, I'm now convinced that
removing the copyable requirement is important, and know how to do it.

In C++17 guaranteed copy elision means this will not copy the do_not_copy in
the first tuple. The X constructor takes it by reference, so there is no copy
there either. (We could actually delete the do_not_copy copy constructor, but
the throwing definition above allows this to work for implementations that
don't support guaranteed copy elision yet).

If we try to do that with a scoped_allocator_adaptor it blows up unless it
guarantees not to copy any of the tuple elements:

With guaranteed copy elision we can initialize the function arguments without
making a copy, but if scoped_allocator_adaptor makes a copy internally by
transforming the tuple<do_not_copy> into tuple<do_not_copy,
allocator<pair>> then we make a copy of the do_not_copy and explode.

So without fixing LWG 2511 this doesn't work. We should fix it both for
efficiency, and for consistency with guaranteed copy elision that will now
happen in more places in C++17.

Simply removing the CopyConstructible requirement isn't sufficient though,
because the tuple_cat operations will make copies. What's needed is to
transform tuple<Args1...> into tuple<Args1&&...>
or tuple<Args1&&..., inner_allocator_type&>
or tuple<allocator_arg_t, innert_allocator_type&, Args1&&...>
as dictated by the uses_allocator_v logic.
i.e. even if the incoming tuples are not tuples of references, the ones that
get passed to pair::pair(piecewise_construct_t, ...) should be tuples of
references.

Alternative solution

Another way to ensure no copies are made would be to replace the
CopyConstructible requirement with a requirement that
conjunction_v<is_reference_v<Args1>..., is_reference_v<Args2>...> is true. If
the incoming tuples are already tuples of references then nothing will be
copied. This has the potential to break some code, whereas the proposal below
doesn't.

Proposed resolution

Changes are relative to N4750.

In [allocator.adaptor.members]

Strike paragraph 10:

Requires: all of the types in Args1 and Args2 shall be CopyConstructible (Table 22).

Modify paragraph 11:

Effects: Constructs a tuple object xprime from x by the following rules:

— If uses_allocator_v<T1, inner_allocator_type> is false and is_constructible_v<T1, Args1...>
is true, then xprime is xtuple<Args1&&...>(std::move(x)).

— Otherwise, if uses_allocator_v<T1, inner_allocator_type> is true and is_constructible_v<T1, Args1..., inner_allocator_type&> is true, then xprime is tuple_cat(tuple<Args1&&...>(std::move(x)), tuple<inner_allocator_type&>(inner_allocator())).

— Otherwise, the program is ill-formed.

and constructs a tuple object yprime from y by the following rules:

— If uses_allocator_v<T2, inner_allocator_type> is false and is_constructible_v<T2, Args2...>
is true, then yprime is ytuple<Args2&&...>(std::move(y)).

— Otherwise, if uses_allocator_v<T2, inner_allocator_type> is true and is_constructible_v<T2, Args2..., inner_allocator_type&> is true, then yprime is tuple_cat(tuple<Args2&&...>(std::move(y)), tuple<inner_allocator_type&>(inner_allocator())).

— Otherwise, the program is ill-formed.

Acknowledgements

Thanks to Tim Song for suggesting the simplified specification in the R1 revision.