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);
Let
—
o = size()
before the call toresize_and_overwrite
.—
k
bemin(o, n)
.—
p
be acharT*
, such that the range [p
,p n
] is valid andthis->compare(0, k, p, k) == 0
istrue
before the call. The values in the range [p k
,p n
] may be indeterminate [basic.indet].—
OP
be the expresionstd::move(op)(p, n)
.—
r = OP
.[...]
- 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?
CodePudding user response:
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.