5

Reading this and this and 23.3.6.5/1 of the standard, where in the latest C++ standard draft is it specified that implementers should prioritize the use of non-throwing move-constructor T(T &&t) noexcept over a const copy-constructor T(const T &t) when std::vector<T> re-allocates its element as a result of a push_back operation? Is it 13.3.3.1.4/1 on overload resolution of reference binding?

EDIT 1

I argue on 13.3.3.1.4/1 because of the following reasons:

  • 13.3/2

    Overload resolution selects the function to call in seven distinct contexts within the language. [...] invocation of a constructor for direct-initialization (8.5) of a class object (13.3.1.3); [...] Each of these contexts defines the set of candidate functions and the list of arguments in its own unique way. But, once the candidate functions and argument lists have been identified, the selection of the best function is the same in all cases: First, a subset of the candidate functions (those that have the proper number of arguments and meet certain other conditions) is selected to form a set of viable functions (13.3.2). Then the best viable function is selected based on the implicit conversion sequences (13.3.3.1) needed to match each argument to the corresponding parameter of each viable function.

  • 13.3.2/1

    From the set of candidate functions constructed for a given context (13.3.1), a set of viable functions is chosen, from which the best function will be selected by comparing argument conversion sequences for the best fit (13.3.3). The selection of viable functions considers relationships between arguments and function parameters other than the ranking of conversion sequences.

  • 13.3.1.3/1

    When objects of class type are direct-initialized (8.5), or copy-initialized from an expression of the same or a derived class type (8.5), overload resolution selects the constructor.

  • 13.3.3.1/5

    For the case where the parameter type is a reference, see 13.3.3.1.4.

  • 13.3.3.1.4/1

    When a parameter of reference type binds directly (8.5.3) to an argument expression, the implicit conversion sequence is the identity conversion, [...]

And therefore, I conclude that the requirement of identity conversion results in requiring the prioritization of T(T &&t) noexcept over T(const T &t). But, the other party I am arguing with is not convinced. So, I ask here.

EDIT 2

The following is the link between 23.3.6.5 and 13.3.3.1.4:

First, 23.3.6.5 requires the following of std::vector:

[...] void push_back(const T& x); void push_back(T&& x); Remarks: [...] If an exception is thrown other than by the copy constructor, move constructor, assignment operator, or move assignment operator of T or by any InputIterator operation there are no effects. [...]

That is covered by 23.3.6.1/2 that requires the following:

A vector satisfies all of the requirements of a container and of a reversible container (given in two tables in 23.2), [...]

That is, std::vector is expected to comply with 23.2.1/7 that specifies the following:

Unless otherwise specified, all containers defined in this clause obtain memory using an allocator (see 17.6.3.5).

and with 23.2.1/3 that specifies the following:

For the components affected by this subclause that declare an allocator_type, objects stored in these components shall be constructed using the allocator_traits<allocator_type>::construct function and destroyed using the allocator_traits<allocator_type>::destroy function (20.7.8.2). These functions are called only for the container’s element type, not for internal types used by the container.

Since 20.7.8.2 specifies only one construct function as follows:

template <class T, class... Args>
static void construct(Alloc& a, T* p, Args&&... args);

with 20.7.8.2/5 requiring the following:

Effects: calls a.construct(p, std::forward<Args>(args)...) if that call is well-formed; otherwise, invokes ::new (static_cast<void*>(p)) T(std::forward<Args>(args)...).

and 20.7.9.2/12 requiring the following of the default allocator:

Effects: ::new((void *)p) U(std::forward<Args>(args)...)

, both T(std::forward<Args>(args)...) in 20.7.8.2/5 and U(std::forward<Args>(args)...) in 20.7.9.2/12 will follow the constructor overload resolution requirement in 13.3.3.1.4/1.

The standard therefore requires the implementers to prioritize the use of move-constructor T(T &&t) noexcept over copy-constructor T(const T &t) when std::vector<T> re-allocates its elements due to push_back operations.

Tadeus Prastowo
  • 267
  • 2
  • 9
  • 1
    Unfortunately I think the answer is no. It's left up to implementors what to do; including pure copy. For non-copy insertable types, of course move is required. – AndyG Sep 25 '17 at 16:38
  • @AndyG: Why 13.3.3.1.4/1 is not an obligation to prioritize the move-constructor over the copy-constructor? – Tadeus Prastowo Sep 25 '17 at 16:41
  • @TadeusPrastowo: [13.3.3.1.4/1 looks to be about choosing reference types based on derived vs. base class](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3797.pdf#page=312): "When a parameter of reference type binds directly (8.5.3) to an argument expression the implicit conversion sequence is the identity conversion, unless the argument expression has a type that is a derived class of the parameter type, in which case the implicit conversion sequence is a derived-to-base Conversion. ..." Doesn't seem related to this choice. – ShadowRanger Sep 25 '17 at 17:11
  • 3
    You might want to paste relevant text here. Following 5 links around isn't all that fun – Passer By Sep 25 '17 at 17:12
  • I'm pretty sure @AndyG is correct, and no requirements are imposed. It's just that any reasonable implementer would go for the (sometimes dramatically) faster move-based approach when it's available; they don't know how expensive move, copy and empty/non-empty destruction are empirically, but the semantics of move followed "empty" destruction are logically lower overhead than copy followed by non-"empty" destruction, so using move preferentially makes sense. – ShadowRanger Sep 25 '17 at 17:20
  • @ShadowRanger: I disagree with you, especially over 13.3.3.1.4/1. Please see my edit. – Tadeus Prastowo Sep 25 '17 at 18:00
  • The standard doesn't specify the exact form of an expression by which old elements would be transferred to new storage during reallocation, so I don't see how the discussion of overload resolution is relevant. The implementation can be explicit about which constructor to invoke, if it so wishes. The standard does provide certain exception safety guarantees, which rule out certain approaches (e.g. it must use copy constructor if move constructor can throw), but it doesn't appear to require that some valid approaches be preferred over others. That's a quality of implementation issue. – Igor Tandetnik Sep 25 '17 at 18:42
  • @IgorTandetnik Please see my second edit under **EDIT 2** that supplies the missing link you pointed out. The link shows that the standard indeed specifies the exact form of an expression by which old elements would be transferred to new storage during reallocation. – Tadeus Prastowo Sep 25 '17 at 21:33
  • @PasserBy Sorry for my initial brevity. I hope I have supplied more details by **EDIT 1** and **EDIT 2**. – Tadeus Prastowo Sep 25 '17 at 21:35
  • 1
    Since it uses perfect forwarding, `construct(Alloc& a, T* p, Args&&... args)` could be tricked into calling either a copy or a move constructor. It all depends on whether an lvalue or an rvalue reference is passed for `args`. That's kind of the point of perfect forwarding. Note that `Args&&` is not an rvalue reference - it's [a forwarding reference](https://isocpp.org/files/papers/N4164.pdf), and can bind either way. – Igor Tandetnik Sep 25 '17 at 21:53
  • @IgorTandetnik I am amazed that you are right. Scrutinizing 23.2.1/13 on the definitions of `MoveInsertable` and `CopyInsertable` as well as the semantics of `push_back` given in Table 101 of 23.2.3/16 along with skimming other requirements on container, sequence container, and vector do not give me any ground to insist on prioritizing the use of nonthrowing move-constructor over const copy-constructor. So, case closed. Thanks. – Tadeus Prastowo Sep 25 '17 at 22:57
  • 1
    @AndyG Your comment gives me the idea of forcing the implementation to use the nonthrowing move-constructor by deleting my const copy-constructor and use `std::vector` operations that only require `MoveInsertable`. – Tadeus Prastowo Sep 25 '17 at 23:12

0 Answers0