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?
One of the main reasons to use C++ is its high performance. An area where we often use the language in a non-efficient way is string handling. C++23 will bring us another string member function that will help us to handle strings in a more performant way.
With C++23, std::string and std::string_view will have similar capabilities. You can call contains () with either a string or a character and it will return true or false depending on whether the queried string or string_view contains the input parameter.
Performance sensitive code is impacted by the cost of initializing and manipulating strings. When writing data into a basic_string, a programmer is faced with an unhappy choice: Pay for extra initialization — resize, which zero initializes, followed by copy. Pay for extra copies — Populate a temporary buffer, copy it to the string.
The authors further recommend UB if op were to throw. resize_and_overwrite is motivated by measured performance improvements that are small individually but significant at scale. Providing exception guarantees would incur overhead that is in tension with the original motivation.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With