3511. Clarify global permission to move

Section: 16.4.5.9 [res.on.arguments] Status: New Submitter: Gonzalo Brito Gadeschi Opened: 2020-12-08 Last modified: 2021-01-15

Priority: 3

View all other issues in [res.on.arguments].

View all issues with New status.

Discussion:

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

The current wording in 16.4.5.9 [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.

[2021-01-15; Telecon prioritization]

Set priority to 3 following reflector and telecon discussions.

Proposed resolution:

This wording is relative to N4868.

  1. Modify 16.4.5.9 [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 (13.10.3.2 [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]