resize_and_overwrite
is overspecified to call its callback with lvaluesSection: 27.4.3.5 [string.capacity] Status: C++23 Submitter: Arthur O'Dwyer Opened: 2021-11-28 Last modified: 2023-11-22
Priority: 2
View all other issues in [string.capacity].
View all issues with C++23 status.
Discussion:
27.4.3.5 [string.capacity] p7 says:
[Let] OP
be the expression std::move(op)(p, n)
.
[Precondition:] OP
does not throw an exception or modify p
or n
.
Notice that p
and n
above are lvalue expressions.
s.resize_and_overwrite(100, [](char*&&, size_t&&){ return 0; });
which is surprising.
B. This wording requires vendors to accepts.resize_and_overwrite(100, [](char*&, size_t&){ return 0; });
which is even more surprising, and also threatens to allow the user to corrupt the internal state (which is why we need to specify the Precondition above).
C. A user who writess.resize_and_overwrite(100, [](auto&&, auto&&){ return 0; });
can detect that they're being passed lvalues instead of rvalues. If we change the wording to permit implementations to pass either lvalues or rvalues (their choice), then this will be detectable by the user, so we don't want that if we can help it.
X. We want to enable implementations to say move(op)(__p, __n)
and then use __p
and __n
.
Y. We have one implementation which wants to say move(op)(data(), __n)
,
which is not currently allowed, but arguably should be.
Z. We have to do or say something about disallowing writes to any
internal state to which Op
might get a reference.
Given all of this, Mark and Arthur think that the simplest way out is to say that the arguments are prvalues. It prevents X, but fixes the surprises in A, B, Y, Z. We could do this in the Let bullets. Either like so:
[Let] p
be a prvalue of type charT*
…
m
be a prvalue of type size_type
equal to n
,
OP
be the expression std::move(op)(p, m)
.
or (Arthur's preference) by specifying prvalues in the expression OP
itself:
[Let] OP
be the expression std::move(op)(auto(p), auto(n))
.
No matter which specification approach we adopt, we can also simplify the Preconditions bullet to:
[Precondition:] OP
does not throw an exception.
because once the user is receiving prvalue copies, it will no longer be physically possible for the
user to modify the library's original variables p
and n
.
[2021-11-29; Arthur O'Dwyer provides wording]
[2022-01-30; Reflector poll]
Set priority to 2 after reflector poll.
Previous resolution [SUPERSEDED]:
This wording is relative to N4901.
Modify 27.4.3.5 [string.capacity] as indicated:
template<class Operation> constexpr void resize_and_overwrite(size_type n, Operation op);-7- Let
(7.1) —
o = size()
before the call toresize_and_overwrite
.(7.2) —
k
bemin(o, n)
.(7.3) —
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 (6.7.4 [basic.indet]).(7.4) —
OP
be the expressionstd::move(op)(auto(p), auto(n))
.(7.5) —
r = OP
.-8- Mandates:
-9- Preconditions:OP
has an integer-like type (24.3.4.4 [iterator.concept.winc]).
(9.1) —
OP
does not throw an exceptionor modify.p
orn
(9.2) —
r ≥ 0
.(9.3) —
r ≤ n
.(9.4) — After evaluating
OP
there are no indeterminate values in the range[p, p + r)
.-10- Effects: Evaluates
-11- Recommended practice: Implementations should avoid unnecessary copies and allocations by, for example, makingOP
, replaces the contents of*this
with[p, p + r)
, and invalidates all pointers and references to the range[p, p + n]
.p
a pointer into internal storage and by restoring*(p + r)
tocharT()
after evaluatingOP
.
[2023-01-11; Jonathan Wakely provides new wording requested by LWG]
[Issaquah 2023-02-07; LWG]
Move to Immediate for C++23
[2023-02-13 Approved at February 2023 meeting in Issaquah. Status changed: Immediate → WP.]
Proposed resolution:
This wording is relative to N4917.
Modify 27.4.3.5 [string.capacity] as indicated:
template<class Operation> constexpr void resize_and_overwrite(size_type n, Operation op);-7- Let
(7.1) —
o = size()
before the call toresize_and_overwrite
.(7.2) —
k
bemin(o, n)
.(7.3) —
p
be a value of typecharT*
orcharT* const
, 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 (6.7.4 [basic.indet]).(7.?) —
m
be a value of typesize_type
orconst size_type
equal ton
.(7.4) —
OP
be the expressionstd::move(op)(p,
.nm)(7.5) —
r = OP
.-8- Mandates:
-9- Preconditions:OP
has an integer-like type (24.3.4.4 [iterator.concept.winc]).
(9.1) —
OP
does not throw an exception or modifyp
or.
nm(9.2) —
r ≥ 0
.(9.3) —
r ≤
.nm(9.4) — After evaluating
OP
there are no indeterminate values in the range[p, p + r)
.-10- Effects: Evaluates
-11- Recommended practice: Implementations should avoid unnecessary copies and allocations by, for example, makingOP
, replaces the contents of*this
with[p, p + r)
, and invalidates all pointers and references to the range[p, p + n]
.p
a pointer into internal storage and by restoring*(p + r)
tocharT()
after evaluatingOP
.