Why does C++23 string::resize_and_overwrite invoke operation as an rvalue?
Asked Answered
D

1

15

In order to improve the performance of writing data into std::string, C++23 specially introduced resize_and_overwrite() for std::string. In [string.capacity], the standard describes it as follows:

template<class Operation>
constexpr void resize_and_overwrite(size_type n, Operation op);
  1. Let

    o = size() before the call to resize_and_overwrite.

    k be min(o, n).

    p be a charT*, such that the range [p, p + n] is valid and this->compare(0, k, p, k) == 0 is true before the call. The values in the range [p + k, p + n] may be indeterminate [basic.indet].

    OP be the expresion std::move(op)(p, n).

    r = OP.

[...]

  1. Effects: Evaluates OP, replaces the contents of *this with [p, p + r), and invalidates all pointers and references to the range [p, p + n].

But I found out that this function will use std::move to convert op into an rvalue before invoking it, which means that we cannot pass in a callable that only has lvalue overloaded operator() (demo):

#include <string>

int main() {
  struct Op {
    int operator()(char*, int) &;
    int operator()(char*, int) && = delete;
  } op;
  std::string s;
  s.resize_and_overwrite(42, op); // ill-formed
}

This behavior seems a bit strange, but since this change was made in the last edition of the paper, it is obviously intentional.

So, what are the considerations behind this? Is there any benefit in the mandate that op must be invoked as an rvalue?

Doc answered 18/10, 2021 at 9:57 Comment(1)
It presumably is to permit calling with a move-only op (such as a one-time-consumable functor). It is relatively unusual to delete the rvalue-qualified operator. If you cannot take advantage of rvalue *this, you usually just fall back to unqualified.Unmixed
W
13

op is only called once before it is destroyed, so calling it as an rvalue permits any && overload on it to reuse any resources it might hold.

The callable object is morally an xvalue - it is "expiring" because it is destroyed immediately after the call. If you specifically designed your callable to only support calling as lvalues, then the library is happy to oblige by preventing this from working.

Welcher answered 18/10, 2021 at 16:38 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.