Title
Clarify global permission to move
Status
new
Section
[res.on.arguments]
Submitter
Gonzalo Brito Gadeschi

Created on 2020-12-21.00:00:00 last changed 3 months ago

Messages

Date: 2021-01-15.21:45:23

Proposed resolution:

This wording is relative to N4868.

  1. Modify [res.on.arguments] as indicated:

    -1- Each of the following applies to all arguments to functions defined in the C++ standard library, unless explicitly stated otherwise.

    1. (1.1) — If an argument to a function has an invalid value (such as a value outside the domain of the function or a pointer invalid for its intended use), the behavior is undefined.

    2. (1.2) — If a function argument is described as being an array, the pointer actually passed to the function shall have a value such that all address computations and accesses to objects (that would be valid if the pointer did point to the first element of such an array) are in fact valid.

    3. (1.3) — If a function argument binds to an rvalue reference parameter, the implementation may assume that this parameter is a unique reference to this argumentthe value within the function's scope and may move from it.

      [Example ?:

      void std_api(int&& a);
      int a;
      std_api(move(a));
      // a is in an unspecified but valid state
      

      end example]

      [Example ?:

      void std_api(int&& a, int&& b);
      int a, b, c;
      std_api(move(a), move(b)); // OK: int&& a and int&& b do not alias
      std_api(move(c), move(c)); // UB: int&& a and int&& b alias
      

      end example]

      [Example ?:

      std::vector<int> a = {...};
      a.push_back(move(42)); // OK: unique reference
      a.push_back(move(a[0])); // UB: (*this)[0] and rvalue argument alias
      

      end example]

      [Note 1: If the parameter is a generic parameter of the form T&& and an lvalue of type A is bound, the argument binds to an lvalue reference ([temp.deduct.call]) and thus is not covered by the previous sentencethis item. — end note] [Note 2: If a program casts an lvalue to an xvalue while passing that lvalue to a library function (e.g., by calling the function with the argument std::move(x)), the program is effectively asking that function to treat that lvalue as a temporary object. The implementation is free to optimize away aliasing checks which might be needed if the argument was an lvalue. — end note]

Date: 2021-01-15.00:00:00

[ 2021-01-15; Telecon prioritization ]

Set priority to 3 following reflector and telecon discussions.

Date: 2020-12-21.00:00:00

The intent of LWG 1204 is to allow standard library APIs accepting rvalue arguments:

  • to move from their arguments, e.g., without having to specify that they might do this as part of their Effects clause, and

  • to assume that rvalue arguments do not alias any pointer in the scope of the standard library API, e.g., to allow vector's push_back(T&& t) to assume that t is not an element of the vector.

The current wording in [res.on.arguments]/1.3 states:

If a function argument binds to an rvalue reference parameter, the implementation may assume that this parameter is a unique reference to this argument.

This sentence is not clear about the scope in which the reference can be assumed to be unique, and it does not explicitly state that the function can modify the argument, e.g., to move from it.

If the scope of the "unique reference" is "whole program scope", this example:

void example(vector<int>& a, int* b) 
{
  int* c = b;            // reference to object pointed at by b
  a.push_back(move(*b)); // UB: rvalue reference aliases c: not unique in whole-program scope
  assert(c == b);        // FAILS: if rvalue reference to *b is unique, b is unique, and c == b is false
}

exhibits UB because the implementation may assume that the reference to b is unique, which does not hold since c is also a reference to b.

If the scope of the "unique reference" is the "function scope" of the standard library API, then the semantics of the rvalue reference argument are very similar to those of C's restrict. This allows aliasing optimizations, for example:

void std_api(int&& a, int&& b); // allowed to assume that a and b do not alias
int a, b, c;
std_api(move(a), move(b)); // OK: two unique references in std_api
std_api(move(c), move(c)); // UB: a and b alias

See llvm Bug 48238 for a bug tracking the implementation of these optimizations in clang.

This also allows optimizing vector::push_back(T&& t) since if t does not alias any pointer in vector::push_back's scope, it also does not alias this, this->data(), (*this)[0], etc.

History
Date User Action Args
2021-01-15 21:45:23adminsetmessages: + msg11653
2020-12-21 16:58:05adminsetmessages: + msg11647
2020-12-21 00:00:00admincreate