Doc. no. | R0165??? |
Date: | Revised 2022-11-24 at 09:59:48 UTC |
Project: | Programming Language C++ |
Reply to: | Jonathan Wakely <lwgchair@gmail.com> |
Section: 32.9 [re.results] Status: Ready Submitter: Daniel Krügler Opened: 2012-10-06 Last modified: 2022-11-10
Priority: 3
View all other issues in [re.results].
View all issues with Ready status.
Discussion:
The requirement expressed in 32.9 [re.results] p2
The class template match_results shall satisfy the requirements of an allocator-aware container and of a sequence container, as specified in 24.2.4 [sequence.reqmts], except that only operations defined for const-qualified sequence containers are supported.
can be read to require the existence of the described constructors from as well, but they do not exist in the synopsis.
The missing sequence constructors are:match_results(initializer_list<value_type>); match_results(size_type, const value_type&); template<class InputIterator> match_results(InputIterator, InputIterator);
The missing allocator-aware container constructors are:
match_results(const match_results&, const Allocator&); match_results(match_results&&, const Allocator&);
It should be clarified, whether (a) constructors are an exception of above mentioned operations or (b) whether at least some of them (like those accepting a match_results value and an allocator) should be added.
As visible in several places of the standard (including the core language), constructors seem usually to be considered as "operations" and they certainly can be invoked for const-qualified objects. The below given proposed resolution applies only the minimum necessary fix, i.e. it excludes constructors from above requirement.[2013-04-20, Bristol]
Check current implementations to see what they do and, possibly, write a paper.
[2013-09 Chicago]
Ask Daniel to update the proposed wording to include the allocator copy and move constructors.
[2014-01-18 Daniel changes proposed resolution]
Previous resolution from Daniel [SUPERSEDED]:
Change 32.9 [re.results] p2 as indicated:
The class template match_results shall satisfy the requirements of an allocator-aware container and of a sequence container, as specified in 24.2.4 [sequence.reqmts], except that only operations defined for const-qualified sequence containers that are not constructors are supported.
[2015-05-06 Lenexa]
MC passes important knowledge to EF.
VV, RP: Looks good.
TK: Second form should be conditionally noexcept
JY: Sequence constructors are not here, but mentioned in the issue writeup. Why?
TK: That would have been fixed by the superseded wording.
JW: How does this interact with Mike Spertus' allocator-aware regexes? [...] Perhaps it doesn't.
JW: Can't create match_results, want both old and new resolution.
JY: It's problematic that users can't create these, but not this issue.
VV: Why conditional noexcept?
MC: Allocator move might throw.
JW: Update superseded wording to "only non-constructor operations that are"?
MC: Only keep superseded, but append "and the means of constructing match_results are limited to [...]"?
JY: Bullet 4 paragraph 2 needs to address the allocator constructor.
Assigned to JW for drafting.
[2015-10, Kona Saturday afternoon]
STL: I want Mike Spertus to be aware of this issue.
Previous resolution from Daniel [SUPERSEDED]:This wording is relative to N3936.
Change 32.9 [re.results] p4, class template match_results synopsis, as indicated:
[…] // 28.10.1, construct/copy/destroy: explicit match_results(const Allocator& a = Allocator()); match_results(const match_results& m); match_results(const match_results& m, const Allocator& a); match_results(match_results&& m) noexcept; match_results(match_results&& m, const Allocator& a) noexcept; […]Change 32.9.2 [re.results.const] as indicated: [Drafting note: Paragraph 6 as currently written, makes not much sense, because the noexcept does not allow any exception to propagate. Further-on, the allocator requirements do not allow for throwing move constructors. Deleting it seems to be near to editorial — end drafting note]
match_results(const match_results& m); match_results(const match_results& m, const Allocator& a);-4- Effects: Constructs an object of class match_results, as a copy of m.
match_results(match_results&& m) noexcept; match_results(match_results&& m, const Allocator& a) noexcept;-5- Effects: Move-constructs an object of class match_results from m satisfying the same postconditions as Table 142.
AdditionallyFor the first form, the stored Allocator value is move constructed from m.get_allocator().-6- Throws: Nothing if the allocator's move constructor throws nothing.
[2019-03-27 Jonathan updates proposed resolution]
Previous resolution [SUPERSEDED]:
This wording is relative to N4810.
These edits overlap with the proposed resolution of 2191 but it should be obvious how to resolve the conflicts. Both resolutions remove the word "Additionally" from p4. Issue 2191 removes the entire Throws: element in p5 but this issue replaces it with different text that applies to the new constructor only.
Change 32.9 [re.results] p4, class template match_results synopsis, as indicated:
[…] // 30.10.1, construct/copy/destroy: explicit match_results(const Allocator& a = Allocator()); match_results(const match_results& m); match_results(const match_results& m, const Allocator& a); match_results(match_results&& m) noexcept; match_results(match_results&& m, const Allocator& a); […]Change 32.9.2 [re.results.const] as indicated:
match_results(const match_results& m); match_results(const match_results& m, const Allocator& a);-3- Effects: Constructs an object of class match_results, as a copy of m. For the second form, the stored Allocator value is constructed from a.
match_results(match_results&& m) noexcept; match_results(match_results&& m, const Allocator& a);-4- Effects: Move-constructs an object of class match_results from m satisfying the same postconditions as Table 128.
AdditionallyFor the first form, the stored Allocator value is move constructed from m.get_allocator(). For the second form, the stored Allocator value is constructed from a.-6- Throws:
Nothing.The second form throws nothing if a == m.get_allocator().
[2022-11-06; Daniel syncs wording with recent working draft]
To ensure that all constructors are consistent in regard to the information about how the stored allocator is constructed, more wording is added. This harmonizes with the way how we specify the individual container constructors (Such as vector) even though 24.2.2.5 [container.alloc.reqmts] already provides some guarantees. For the copy-constructor we intentionally refer to 24.2.2.2 [container.reqmts] so that we don't need to repeat what is said there.
[Kona 2022-11-08; Move to Ready]
Proposed resolution:
This wording is relative to N4917.
Change 32.9 [re.results], class template match_results synopsis, as indicated:
[…] // 32.9.2 [re.results.const], construct/copy/destroy: match_results() : match_results(Allocator()) {} explicit match_results(const Allocator& a); match_results(const match_results& m); match_results(const match_results& m, const Allocator& a); match_results(match_results&& m) noexcept; match_results(match_results&& m, const Allocator& a); […]
Change 32.9.2 [re.results.const] as indicated:
explicit match_results(const Allocator& a);-?- Effects: The stored Allocator value is constructed from a.
-2- Postconditions: ready() returns false. size() returns 0.match_results(const match_results& m); match_results(const match_results& m, const Allocator& a);-?- Effects: For the first form, the stored Allocator value is obtained as specified in 24.2.2.2 [container.reqmts]. For the second form, the stored Allocator value is constructed from a.
-3- Postconditions: As specified in Table 142 [tab:re.results.const].match_results(match_results&& m) noexcept; match_results(match_results&& m, const Allocator& a);-4- Effects: For the first form, t
-5- Postconditions: As specified in Table 142 [tab:re.results.const]. -?- Throws: The second form throws nothing if a == m.get_allocator() is true.The stored Allocator value is move constructed from m.get_allocator(). For the second form, the stored Allocator value is constructed from a.
Section: 30.3.1.3 [locale.cons] Status: Ready Submitter: Juan Soulie Opened: 2013-09-04 Last modified: 2022-11-09
Priority: 3
View other active issues in [locale.cons].
View all other issues in [locale.cons].
View all issues with Ready status.
Discussion:
30.3.1.3 [locale.cons] p14 ends with:
"[…] If f is null, the resulting object is a copy of other."
but the next line p15 says:
"Remarks: The resulting locale has no name."
But both can't be true when other has a name and f is null.
I've tried it on two implementations (MSVC,GCC) and they are inconsistent with each other on this.Daniel Krügler:
As currently written, the Remarks element applies unconditionally for all cases and thus should "win". The question arises whether the introduction of this element by LWG 424 had actually intended to change the previous Note to a Remarks element. In either case the wording should be improved to clarify this special case.[2022-02-14; Daniel comments]
This issue seems to have some overlap with LWG 3676 so both should presumably be resolved in a harmonized way.
[2022-11-01; Jonathan provides wording]
This also resolves 3673 and 3676.
[2022-11-04; Jonathan revises wording after feedback]
Revert an incorrect edit to p8, which was incorrectly changed to:
"If cats is equal to locale::none, the resulting locale has the same name as locale(std_name). Otherwise, the locale has a name if and only if other has a name."
[Kona 2022-11-08; Move to Ready status]
Proposed resolution:
This wording is relative to N4917.
Modify 30.3.1.3 [locale.cons] as indicated:
explicit locale(const char* std_name);-2- Effects: Constructs a locale using standard C locale names, e.g., "POSIX". The resulting locale implements semantics defined to be associated with that name.
-3- Throws: runtime_error if the argument is not valid, or is null.
-4- Remarks: The set of valid string argument values is "C", "", and any implementation-defined values.
explicit locale(const string& std_name);-5- Effects:
The same asEquivalent to locale(std_name.c_str()).locale(const locale& other, const char* std_name, category cats);-?- Preconditions: cats is a valid category value (30.3.1.2.1 [locale.category]).
-6- Effects: Constructs a locale as a copy of other except for the facets identified by the category argument, which instead implement the same semantics as locale(std_name).
-7- Throws: runtime_error if the second argument is not valid, or is null.
-8- Remarks: The locale has a name if and only if other has a name.
locale(const locale& other, const string& std_name, category cats);-9- Effects:
The same asEquivalent to locale(other, std_name.c_str(), cats).template<class Facet> locale(const locale& other, Facet* f);-10- Effects: Constructs a locale incorporating all facets from the first argument except that of type Facet, and installs the second argument as the remaining facet. If f is null, the resulting object is a copy of other.
-11- Remarks: If f is null, the resulting locale has the same name as other. Otherwise, the
Theresulting locale has no name.locale(const locale& other, const locale& one, category cats);-?- Preconditions: cats is a valid category value.
-12- Effects: Constructs a locale incorporating all facets from the first argument except for those that implement cats, which are instead incorporated from the second argument.
-13- Remarks: If cats is equal to locale::none, the resulting locale has a name if and only if the first argument has a name. Otherwise, the
Thelocale has a name if and only if the first two arguments both have names.
Section: 27.8.8 [alg.heap.operations] Status: Ready Submitter: Robert Douglas Opened: 2017-11-08 Last modified: 2022-11-10
Priority: 3
View all other issues in [alg.heap.operations].
View all issues with Ready status.
Discussion:
In discussion of D0202R3 in Albuquerque, it was observed that pop_heap and sort_heap had constexpr removed for their requirement of ValueSwappable. It was then observed that push_heap and make_heap were not similarly marked as having the ValueSwappable requirement. The room believed this was likely a specification error, and asked to open an issue to track it.
[2017-11 Albuquerque Wednesday night issues processing]
Priority set to 3; Marshall to investigate
Previous resolution [SUPERSEDED]:
This wording is relative to N4700.
Change 27.8.8.2 [push.heap] as indicated:
template<class RandomAccessIterator> void push_heap(RandomAccessIterator first, RandomAccessIterator last); template<class RandomAccessIterator, class Compare> void push_heap(RandomAccessIterator first, RandomAccessIterator last, Compare comp);-1- Requires: The range [first, last - 1) shall be a valid heap. RandomAccessIterator shall satisfy the requirements of ValueSwappable (16.4.4.3 [swappable.requirements]). The type of *first shall satisfy the MoveConstructible requirements (Table 23) and the MoveAssignable requirements (Table 25).
Change 27.8.8.4 [make.heap] as indicated:
template<class RandomAccessIterator> void make_heap(RandomAccessIterator first, RandomAccessIterator last); template<class RandomAccessIterator, class Compare> void make_heap(RandomAccessIterator first, RandomAccessIterator last, Compare comp);-1- Requires: RandomAccessIterator shall satisfy the requirements of ValueSwappable (16.4.4.3 [swappable.requirements]). The type of *first shall satisfy the MoveConstructible requirements (Table 23) and the MoveAssignable requirements (Table 25).
[2022-11-06; Daniel comments and syncs wording with recent working draft]
For reference, the finally accepted paper was P0202R3 and the constexpr-ification of swap-related algorithms had been realized later by P0879R0 after resolution of CWG 1581 and more importantly CWG 1330.
[Kona 2022-11-09; Move to Ready]
Proposed resolution:
This wording is relative to N4917.
Change 27.8.8.2 [push.heap] as indicated:
template<class RandomAccessIterator> constexpr void push_heap(RandomAccessIterator first, RandomAccessIterator last); template<class RandomAccessIterator, class Compare> constexpr void push_heap(RandomAccessIterator first, RandomAccessIterator last, Compare comp); template<random_access_iterator I, sentinel_for<I> S, class Comp = ranges::less, class Proj = identity> requires sortable<I, Comp, Proj> constexpr I ranges::push_heap(I first, S last, Comp comp = {}, Proj proj = {}); template<random_access_range R, class Comp = ranges::less, class Proj = identity> requires sortable<iterator_t<R>, Comp, Proj> constexpr borrowed_iterator_t<R> ranges::push_heap(R&& r, Comp comp = {}, Proj proj = {});-1- Let comp be less{} and proj be identity{} for the overloads with no parameters by those names.
-2- Preconditions: The range [first, last - 1) is a valid heap with respect to comp and proj. For the overloads in namespace std, RandomAccessIterator meets the Cpp17ValueSwappable requirements (16.4.4.3 [swappable.requirements]) and the type of *first meets the Cpp17MoveConstructible requirements (Table 32) and the Cpp17MoveAssignable requirements (Table 34). -3- Effects: Places the value in the location last - 1 into the resulting heap [first, last). -4- Returns: last for the overloads in namespace ranges. -5- Complexity: At most log(last - first) comparisons and twice as many projections.
Change 27.8.8.4 [make.heap] as indicated:
template<class RandomAccessIterator> constexpr void make_heap(RandomAccessIterator first, RandomAccessIterator last); template<class RandomAccessIterator, class Compare> constexpr void make_heap(RandomAccessIterator first, RandomAccessIterator last, Compare comp); template<random_access_iterator I, sentinel_for<I> S, class Comp = ranges::less, class Proj = identity> requires sortable<I, Comp, Proj> constexpr I ranges::make_heap(I first, S last, Comp comp = {}, Proj proj = {}); template<random_access_range R, class Comp = ranges::less, class Proj = identity> requires sortable<iterator_t<R>, Comp, Proj> constexpr borrowed_iterator_t<R> ranges::make_heap(R&& r, Comp comp = {}, Proj proj = {});-1- Let comp be less{} and proj be identity{} for the overloads with no parameters by those names.
-2- Preconditions: For the overloads in namespace std, RandomAccessIterator meets the Cpp17ValueSwappable requirements (16.4.4.3 [swappable.requirements]) and the type of *first meets the Cpp17MoveConstructible (Table 32) and Cpp17MoveAssignable (Table 34) requirements. -3- Effects: Constructs a heap with respect to comp and proj out of the range [first, last). -4- Returns: last for the overloads in namespace ranges. -5- Complexity: At most 3(last - first) comparisons and twice as many projections.
Section: 23.2.2 [char.traits.require] Status: Ready Submitter: Jonathan Wakely Opened: 2018-03-16 Last modified: 2022-11-11
Priority: 2
View other active issues in [char.traits.require].
View all other issues in [char.traits.require].
View all issues with Ready status.
Discussion:
Table 54, Character traits requirements, says that char_traits::move allows the ranges to overlap, but char_traits::copy requires that p is not in the range [s, s + n). This appears to be an attempt to map to the requirements of memmove and memcpy respectively, allowing those to be used to implement the functions, however the requirements for copy are weaker than those for memcpy. The C standard says for memcpy "If copying takes place between objects that overlap, the behavior is undefined" which is a stronger requirement than the start of the source range not being in the destination range.
All of libstdc++, libc++ and VC++ simply use memcpy for char_traits<char>::copy, resulting in undefined behaviour in this example:char p[] = "abc"; char* s = p + 1; std::char_traits<char>::copy(s, p, 2); assert(std::char_traits<char>::compare(p, "aab", 3) == 0);
If the intention is to allow memcpy as a valid implementation then the precondition is wrong (unfortunately nobody realized this when fixing char_traits::move in LWG DR 7). If the intention is to require memmove then it is strange to have separate copy and move functions that both use memmove.
N.B. std::copy and std::copy_backward are not valid implementations of char_traits::copy either, due to different preconditions. Changing the precondition implicitly applies to basic_string::copy (23.4.3.7.7 [string.copy]), and basic_string_view::copy (23.3.3.8 [string.view.ops]), which are currently required to support partially overlapping ranges:std::string s = "abc"; s.copy(s.data() + 1, s.length() - 1); assert(s == "aab");
[2018-04-03 Priority set to 2 after discussion on the reflector.]
[2018-08-23 Batavia Issues processing]
No consensus for direction; revisit in San Diego. Status to Open.
[2022-04-25; Daniel rebases wording on N4910]
Previous resolution [SUPERSEDED]:
This wording is relative to N4910.
Option A:
Edit 23.2.2 [char.traits.require], Table 75 — "Character traits requirements" [tab:char.traits.req], as indicated:
Table 75 — Character traits requirements [tab:char.traits.req] Expression Return type Assertion/note
pre/post-conditionComplexity […] X::copy(s,p,n) X::char_type* Preconditions: p not in [s,s+n)The ranges [p,p+n)
and [s,s+n) do not overlap.
Returns: s.
for each i in [0,n), performs
X::assign(s[i],p[i]).linear […] Option B:
NAD (i.e. implementations need to be fixed, in practice char_traits::copy and char_traits::move might be equivalent).
[Kona 2022-11-11; Move to Ready]
LWG voted for Option A (6 for, 0 against, 1 netural)
Proposed resolution:
Edit 23.2.2 [char.traits.require], Table 75 — "Character traits requirements" [tab:char.traits.req], as indicated:
Table 75 — Character traits requirements [tab:char.traits.req] Expression Return type Assertion/note
pre/post-conditionComplexity […] X::copy(s,p,n) X::char_type* Preconditions: p not in [s,s+n)The ranges [p,p+n)
and [s,s+n) do not overlap.
Returns: s.
for each i in [0,n), performs
X::assign(s[i],p[i]).linear […]
Section: 25.4.4.3 [range.iter.op.distance] Status: Ready Submitter: Arthur O'Dwyer Opened: 2022-01-23 Last modified: 2022-11-10
Priority: 2
View all other issues in [range.iter.op.distance].
View all issues with Ready status.
Discussion:
Consider the use of std::ranges::distance(first, last) on a simple C array. This works fine with std::distance, but currently does not work with std::ranges::distance.
// godbolt link #include <ranges> #include <cassert> int main() { int a[] = {1, 2, 3}; assert(std::ranges::distance(a, a+3) == 3); assert(std::ranges::distance(a, a) == 0); assert(std::ranges::distance(a+3, a) == -3); }
Before LWG 3392, we had a single iterator-pair overload:
template<input_or_output_iterator I, sentinel_for<I> S> constexpr iter_difference_t<I> distance(I first, S last);
which works fine for C pointers. After LWG 3392, we have two iterator-pair overloads:
template<input_or_output_iterator I, sentinel_for<I> S> requires (!sized_sentinel_for<S, I>) constexpr iter_difference_t<I> distance(I first, S last); template<input_or_output_iterator I, sized_sentinel_for<I> S> constexpr iter_difference_t<I> distance(const I& first, const S& last);
and unfortunately the one we want — distance(I first, S last) — is no longer viable because [with I=int*, S=int*], we have sized_sentinel_for<S, I> and so its constraints aren't satisfied. So we look at the other overload [with I=int[3], S=int[3]], but unfortunately its constraints aren't satisfied either, because int[3] is not an input_or_output_iterator.
[2022-01-30; Reflector poll]
Set priority to 2 after reflector poll.
Previous resolution [SUPERSEDED]:
This wording is relative to N4901.
[Drafting Note: Thanks to Casey Carter. Notice that sentinel_for<S, I> already implies and subsumes input_or_output_iterator<I>, so that constraint wasn't doing anything; personally I'd prefer to remove it for symmetry (and to save the environment). Otherwise you'll have people asking why one of the I's is constrained and the other isn't.]
Modify 25.2 [iterator.synopsis], header <iterator> synopsis, as indicated:
[…] // 25.4.4.3 [range.iter.op.distance], ranges::distance template<classinput_or_output_iteratorI, sentinel_for<I> S> requires (!sized_sentinel_for<S, I>) constexpr iter_difference_t<I> distance(I first, S last); template<classinput_or_output_iteratorI, sized_sentinel_for<decay_t<I>> S> constexpr iter_difference_t<I> distance(const I& first,constS&last); […]Modify 25.4.4.3 [range.iter.op.distance] as indicated:
template<classinput_or_output_iteratorI, sentinel_for<I> S> requires (!sized_sentinel_for<S, I>) constexpr iter_difference_t<I> ranges::distance(I first, S last);-1- Preconditions: [first, last) denotes a range.
-2- Effects: Increments first until last is reached and returns the number of increments.template<classinput_or_output_iteratorI, sized_sentinel_for<decay_t<I>> S> constexpr iter_difference_t<I> ranges::distance(const I& first,constS&last);-3- Effects: Equivalent to: return last - first;
[2022-02-16; Arthur and Casey provide improved wording]
[Kona 2022-11-08; Move to Ready]
Proposed resolution:
This wording is relative to N4901.
[Drafting Note: Arthur thinks it's a bit "cute" of the Effects: element to static_cast from T(&)[N] to T* const& in the array case, but it does seem to do the right thing in all cases, and it saves us from having to use an if constexpr (is_array_v...) or something like that.]
Modify 25.2 [iterator.synopsis], header <iterator> synopsis, as indicated:
[…] // 25.4.4.3 [range.iter.op.distance], ranges::distance template<classinput_or_output_iteratorI, sentinel_for<I> S> requires (!sized_sentinel_for<S, I>) constexpr iter_difference_t<I> distance(I first, S last); template<classinput_or_output_iteratorI, sized_sentinel_for<decay_t<I>> S> constexpr iter_difference_t<decay_t<I>> distance(constI&& first,constS&last); […]
Modify 25.4.4.3 [range.iter.op.distance] as indicated:
template<classinput_or_output_iteratorI, sentinel_for<I> S> requires (!sized_sentinel_for<S, I>) constexpr iter_difference_t<I> ranges::distance(I first, S last);-1- Preconditions: [first, last) denotes a range.
-2- Effects: Increments first until last is reached and returns the number of increments.template<classinput_or_output_iteratorI, sized_sentinel_for<decay_t<I>> S> constexpr iter_difference_t<decay_t<I>> ranges::distance(constI&& first,constS&last);-3- Effects: Equivalent to: return last - static_cast<const decay_t<I>&>(first);
Section: 22.14.2.2 [format.string.std] Status: Ready Submitter: Mark de Wever Opened: 2022-06-19 Last modified: 2022-11-10
Priority: 2
View other active issues in [format.string.std].
View all other issues in [format.string.std].
View all issues with Ready status.
Discussion:
Per 22.14.2.2 [format.string.std]/7
If { arg-idopt } is used in a width or precision, the value of the corresponding formatting argument is used in its place. If the corresponding formatting argument is not of integral type, or its value is negative for precision or non-positive for width, an exception of type format_error is thrown.
The issue is the integral type requirement. The following code is currently valid:
std::cout << std::format("{:*^{}}\n", 'a', '0'); std::cout << std::format("{:*^{}}\n", 'a', true);
The output of the first example depends on the value of '0' in the implementation. When a char has signed char as underlying type negative values are invalid, while the same value would be valid when the underlying type is unsigned char. For the second example the range of a boolean is very small, so this seems not really useful.
Currently libc++ rejects these two examples and MSVC STL accepts them. The members of the MSVC STL team, I spoke, agree these two cases should be rejected. The following integral types are rejected by both libc++ and MSVC STL:std::cout << std::format("{:*^{}}\n", 'a', L'0'); std::cout << std::format("{:*^{}}\n", 'a', u'0'); std::cout << std::format("{:*^{}}\n", 'a', U'0'); std::cout << std::format("{:*^{}}\n", 'a', u8'0');
In order to accept these character types they need to meet the basic formatter requirements per 22.14.5 [format.functions]/20 and 22.14.5 [format.functions]/25
formatter<remove_cvref_t<Ti>, charT> meets the BasicFormatter requirements (22.14.6.1 [formatter.requirements]) for each Ti in Args.
which requires adding the following enabled formatter specializations to 22.14.6.3 [format.formatter.spec].
template<> struct formatter<wchar_t, char>; template<> struct formatter<char8_t, charT>; template<> struct formatter<char16_t, charT>; template<> struct formatter<char32_t, charT>;
Note, that the specialization template<> struct formatter<char, wchar_t> is already required by the Standard.
Not only do they need to be added, but it also needs to be specified how they behave when their value is not in the range of representable values for charT. Instead of requiring these specializations, I propose to go the other direction and limit the allowed types to signed and unsigned integers.[2022-07-08; Reflector poll]
Set priority to 2 after reflector poll. Tim Song commented:
"This is technically a breaking change, so we should do it sooner rather than later.
"I don't agree with the second part of the argument though - I don't see how this wording requires adding those transcoding specializations. Nothing in this wording requires integral types that cannot be packed into basic_format_arg to be accepted.
"I also think we need to restrict this to signed or unsigned integer types with size no greater than sizeof(long long). Larger types get type-erased into a handle and the value isn't really recoverable without heroics."
Previous resolution [SUPERSEDED]:
This wording is relative to N4910.
Modify 22.14.2.2 [format.string.std] as indicated:
-7- If { arg-idopt } is used in a width or precision, the value of the corresponding formatting argument is used in its place. If the corresponding formatting argument is not of
integralsigned or unsigned integer type, or its value is negative for precision or non-positive for width, an exception of type format_error is thrown.Add a new paragraph to C.1.8 [diff.cpp20.utilities] as indicated:
Affected subclause: 22.14 [format]
Change: Requirement changes of arg-id of the width and precision fields of std-format-spec. arg-id now requires a signed or unsigned integer type instead of an integral type. Rationale: Avoid types that are not useful and the need to specify enabled formatter specializations for all character types. Effect on original feature: Valid C++ 2020 code that passes a boolean or character type as arg-id becomes invalid. For example:std::format("{:*^{}}", "", true); // ill-formed, previously returned "*"
[2022-11-01; Jonathan provides improved wording]
Previous resolution [SUPERSEDED]:
This wording is relative to N4917.
Modify 22.14.2.2 [format.string.std] as indicated:
-8- If { arg-idopt } is used in a width or precision, the value of the corresponding formatting argument is used in its place. If the corresponding formatting argument is not of
integralstandard signed or unsigned integer type, or its value is negative, an exception of type format_error is thrown.Add a new paragraph to C.1.8 [diff.cpp20.utilities] as indicated:
Affected subclause: 22.14.2.2 [format.string.std]
Change: Restrict types of formatting arguments used as width or precision in a std-format-spec. Rationale: Avoid types that are not useful or do not have portable semantics. Effect on original feature: Valid C++ 2020 code that passes a boolean or character type as arg-id becomes invalid. For example:std::format("{:*^{}}", "", true); // ill-formed, previously returned "*" std::format("{:*^{}}", "", '1'); // ill-formed, previously returned an implementation-defined number of '*' characters
[2022-11-10; Jonathan revises wording]
Improve Annex C entry.
[Kona 2022-11-10; Move to Ready]
Proposed resolution:
This wording is relative to N4917.
Modify 22.14.2.2 [format.string.std] as indicated:
-8- If { arg-idopt } is used in a width or precision, the value of the corresponding formatting argument is used in its place. If the corresponding formatting argument is not of
integralstandard signed or unsigned integer type, or its value is negative, an exception of type format_error is thrown.
Add a new paragraph to C.1.8 [diff.cpp20.utilities] as indicated:
Affected subclause: 22.14.2.2 [format.string.std]
Change: Restrict types of formatting arguments used as width or precision in a std-format-spec. Rationale: Disallow types that do not have useful or portable semantics as a formatting width or precision. Effect on original feature: Valid C++ 2020 code that passes a boolean or character type as arg-id becomes invalid. For example:std::format("{:*^{}}", "", true); // ill-formed, previously returned "*" std::format("{:*^{}}", "", '1'); // ill-formed, previously returned an implementation-defined number of '*' characters
Section: 17.14.5 [support.signal], 33.5.10 [atomics.flag] Status: Ready Submitter: Ruslan Baratov Opened: 2022-08-18 Last modified: 2022-11-11
Priority: 3
View all issues with Ready status.
Discussion:
Following document number N4910 about signal-safe instructions 17.14.5 [support.signal] Signal handlers, and it's unclear whether std::atomic_flag is signal-safe.
Formally it doesn't fit any of the mentioned conditions:f is a non-static member function invoked on an object A, such that A.is_lock_free() yields true, or
(there is no is_lock_free method in std::atomic_flag class)
f is a non-member function, and for every pointer-to-atomic argument A passed to f, atomic_is_lock_free(A) yields true
(std::atomic_flag object can't be passed to atomic_is_lock_free as argument)
However, std::atomic_flag seem to fit well here, it's atomic, and it's always lock-free.
The suggestion is as follows: If std::atomic_flag is signal-safe, then it should be explicitly mentioned in 17.14.5 [support.signal], e.g., the following lines should be added:
f is a non-static member function invoked on an atomic_flag object, or
f is a non-member function, and every pointer-to-atomic argument passed to f is atomic_flag, or
If the std::atomic_flag is not signal-safe, the following note could be added:
[Note: Even though atomic_flag is atomic and lock-free, it's not signal-safe. — end note]
[2022-09-23; Reflector poll]
Set priority to 3 after reflector poll. Send to SG1.
Another way to fix this is to add is_always_lock_free (=true) and is_lock_free() { return true; } to atomic_flag.
[Kona 2022-11-10; SG1 yields a recommendation]
Poll: Adopt the proposed resolution for LWG3756
"f is a non-static member function invoked on an
atomic_flag object, or"
"f is a non-member function, and every pointer-to-
atomic argument passed to f is atomic_flag, or"
SF F N A SA 11 3 0 0 0
Unanimous consent
Previous resolution [SUPERSEDED]:
This wording is relative to N4917.
Modify 17.14.5 [support.signal] as indicated:
-1- A call to the function signal synchronizes with any resulting invocation of the signal handler so installed.
-2- A plain lock-free atomic operation is an invocation of a function f from 33.5 [atomics], such that:
- (2.1) — f is the function atomic_is_lock_free(), or
- (2.2) — f is the member function is_lock_free(), or
- (2.?) — f is a non-static member function invoked on an atomic_flag object, or
- (2.?) — f is a non-member function, and every pointer-to-atomic argument passed to f is atomic_flag, or
- (2.3) — f is a non-static member function invoked on an object A, such that A.is_lock_free() yields true, or
- (2.4) — f is a non-member function, and for every pointer-to-atomic argument A passed to f, atomic_is_lock_free(A) yields true.
-3- An evaluation is signal-safe unless it includes one of the following:
- (3.1) — a call to any standard library function, except for plain lock-free atomic operations and functions explicitly identified as signal-safe;
[Note 1: This implicitly excludes the use of new and delete expressions that rely on a library-provided memory allocator. — end note]
- (3.2) — an access to an object with thread storage duration;
- (3.3) — a dynamic_cast expression;
- (3.4) — throwing of an exception;
- (3.5) — control entering a try-block or function-try-block;
- (3.6) — initialization of a variable with static storage duration requiring dynamic initialization (6.9.3.3 [basic.start.dynamic], 8.8 [stmt.dcl])206; or
- (3.7) — waiting for the completion of the initialization of a variable with static storage duration (8.8 [stmt.dcl]).
A signal handler invocation has undefined behavior if it includes an evaluation that is not signal-safe.
[2022-11-11; Jonathan provides improved wording]
[Kona 2022-11-11; Move to Ready]
Proposed resolution:
This wording is relative to N4917.
Modify 17.14.5 [support.signal] as indicated:
-1- A call to the function signal synchronizes with any resulting invocation of the signal handler so installed.
-2- A plain lock-free atomic operation is an invocation of a function f from 33.5 [atomics], such that:
- (2.1) — f is the function atomic_is_lock_free(), or
- (2.2) — f is the member function is_lock_free(), or
- (2.?) — f is a non-static member function of class atomic_flag, or
- (2.?) — f is a non-member function, and the first parameter of f has type cv atomic_flag*, or
- (2.3) — f is a non-static member function invoked on an object A, such that A.is_lock_free() yields true, or
- (2.4) — f is a non-member function, and for every pointer-to-atomic argument A passed to f, atomic_is_lock_free(A) yields true.
-3- An evaluation is signal-safe unless it includes one of the following:
- (3.1) — a call to any standard library function, except for plain lock-free atomic operations and functions explicitly identified as signal-safe;
[Note 1: This implicitly excludes the use of new and delete expressions that rely on a library-provided memory allocator. — end note]
- (3.2) — an access to an object with thread storage duration;
- (3.3) — a dynamic_cast expression;
- (3.4) — throwing of an exception;
- (3.5) — control entering a try-block or function-try-block;
- (3.6) — initialization of a variable with static storage duration requiring dynamic initialization (6.9.3.3 [basic.start.dynamic], 8.8 [stmt.dcl])206; or
- (3.7) — waiting for the completion of the initialization of a variable with static storage duration (8.8 [stmt.dcl]).
A signal handler invocation has undefined behavior if it includes an evaluation that is not signal-safe.
Section: 25.5.3 [const.iterators] Status: Ready Submitter: Hewill Kang Opened: 2022-09-05 Last modified: 2022-11-10
Priority: 1
View all issues with Ready status.
Discussion:
Currently, basic_const_iterator::operator== is defined as a friend function:
template<sentinel_for<Iterator> S> friend constexpr bool operator==(const basic_const_iterator& x, const S& s);
which only requires S to model sentinel_for<Iterator>, and since basic_const_iterator has a conversion constructor that accepts I, this will result in infinite constraint checks when comparing basic_const_iterator<int*> with int* (online example):
#include <iterator>
template<std::input_iterator I>
struct basic_const_iterator {
basic_const_iterator() = default;
basic_const_iterator(I);
template<std::sentinel_for<I> S>
friend bool operator==(const basic_const_iterator&, const S&);
};
static_assert(std::sentinel_for<basic_const_iterator<int*>, int*>); // infinite meta-recursion
That is, sentinel_for ends with weakly-equality-comparable-with and instantiates operator==, which in turn rechecks sentinel_for and instantiates the same operator==, making the circle closed.
The proposed resolution is to change operator== to be a member function so that S is no longer accidentally instantiated as basic_const_iterator. The same goes for basic_const_iterator::operator-.[2022-09-23; Reflector poll]
Set priority to 1 after reflector poll.
"Although I am not a big fan of member ==, the proposed solution seems to be simple." "prefer if we would keep operator== as non-member for consistency."
Previous resolution from Hewill [SUPERSEDED]:This wording is relative to N4917.
Modify 25.5.3.3 [const.iterators.iterator], class template basic_const_iterator synopsis, as indicated:
namespace std { template<class I> concept not-a-const-iterator = see below; template<input_iterator Iterator> class basic_const_iterator { Iterator current_ = Iterator(); using reference = iter_const_reference_t<Iterator>; // exposition only public: […] template<sentinel_for<Iterator> S>friendconstexpr bool operator==(const basic_const_iterator& x,const S& s) const; […] template<sized_sentinel_for<Iterator> S>friendconstexpr difference_type operator-(const basic_const_iterator& x,const S& y) const; template<not-a-const-iteratorsized_sentinel_for<Iterator>S> requires sized_sentinel_fordifferent-from<S, Iteratorbasic_const_iterator> friend constexpr difference_type operator-(const S& x, const basic_const_iterator& y); }; }Modify 25.5.3.5 [const.iterators.ops] as indicated:
[…]
template<sentinel_for<Iterator> S>friendconstexpr bool operator==(const basic_const_iterator& x,const S& s) const;[…]-16- Effects: Equivalent to: return
x.current_ == s;.template<sized_sentinel_for<Iterator> S>friendconstexpr difference_type operator-(const basic_const_iterator& x,const S& y) const;-24- Effects: Equivalent to: return
x.current_ - y;.template<not-a-const-iteratorsized_sentinel_for<Iterator>S> requires sized_sentinel_fordifferent-from<S, Iteratorbasic_const_iterator> friend constexpr difference_type operator-(const S& x, const basic_const_iterator& y);-25- Effects: Equivalent to: return x - y.current_;.
[2022-11-04; Tomasz comments and improves proposed wording]
Initially, LWG requested an investigation of alternative resolutions that would avoid using member functions for the affected operators. Later, it was found that in addition to ==/-, all comparison operators (<, >, <=, >=, <=>) are affected by same problem for the calls with basic_const_iterator<basic_const_iterator<int*>> and int* as arguments, i.e. totally_ordered_with<basic_const_iterator<basic_const_iterator<int*>>, int*> causes infinite recursion in constraint checking.
The new resolution, change all of the friends overloads for operators ==, <, >, <=, >=, <=> and - that accept basic_const_iterator as lhs, to const member functions. This change is applied to homogeneous (basic_const_iterator, basic_const_iterator) for consistency. For the overload of <, >, <=, >= and - that accepts (I, basic_const_iterator) we declared them as friends and consistently constrain them with not-const-iterator. Finally, its put (now member) operator<=>(I) in the block with other heterogeneous overloads in the synopsis.
The use of member functions addresses issues, because:
[Kona 2022-11-08; Move to Ready]
Proposed resolution:
This wording is relative to N4917.
Modify 25.5.3.3 [const.iterators.iterator], class template basic_const_iterator synopsis, as indicated:
namespace std { template<class I> concept not-a-const-iterator = see below; template<input_iterator Iterator> class basic_const_iterator { Iterator current_ = Iterator(); using reference = iter_const_reference_t<Iterator>; // exposition only public: […] template<sentinel_for<Iterator> S>friendconstexpr bool operator==(const basic_const_iterator& x,const S& s) const;friendconstexpr bool operator<(const basic_const_iterator& x,const basic_const_iterator& y) const requires random_access_iterator<Iterator>;friendconstexpr bool operator>(const basic_const_iterator& x,const basic_const_iterator& y) const requires random_access_iterator<Iterator>;friendconstexpr bool operator<=(const basic_const_iterator& x,const basic_const_iterator& y) const requires random_access_iterator<Iterator>;friendconstexpr bool operator>=(const basic_const_iterator& x,const basic_const_iterator& y) const requires random_access_iterator<Iterator>;friendconstexpr auto operator<=>(const basic_const_iterator& x,const basic_const_iterator& y) const requires random_access_iterator<Iterator> && three_way_comparable<Iterator>; template<different-from<basic_const_iterator> I>friendconstexpr bool operator<(const basic_const_iterator& x,const I& y) const requires random_access_iterator<Iterator> && totally_ordered_with<Iterator, I>; template<different-from<basic_const_iterator> I>friendconstexpr bool operator>(const basic_const_iterator& x,const I& y) const requires random_access_iterator<Iterator> && totally_ordered_with<Iterator, I>; template<different-from<basic_const_iterator> I>friendconstexpr bool operator<=(const basic_const_iterator& x,const I& y) const requires random_access_iterator<Iterator> && totally_ordered_with<Iterator, I>; template<different-from<basic_const_iterator> I>friendconstexpr bool operator>=(const basic_const_iterator& x,const I& y) const requires random_access_iterator<Iterator> && totally_ordered_with<Iterator, I>; template<different-from<basic_const_iterator> I> constexpr auto operator<=>(const I& y) const requires random_access_iterator<Iterator> && totally_ordered_with<Iterator, I> && three_way_comparable_with<Iterator, I>; template<not-a-const-iterator I> friend constexpr bool operator<(const I& y, const basic_const_iterator& x) requires random_access_iterator<Iterator> && totally_ordered_with<Iterator, I>; template<not-a-const-iterator I> friend constexpr bool operator>(const I& y, const basic_const_iterator& x) requires random_access_iterator<Iterator> && totally_ordered_with<Iterator, I>; template<not-a-const-iterator I> friend constexpr bool operator<=(const I& y, const basic_const_iterator& x) requires random_access_iterator<Iterator> && totally_ordered_with<Iterator, I>; template<not-a-const-iterator I> friend constexpr bool operator>=(const I& y, const basic_const_iterator& x) requires random_access_iterator<Iterator> && totally_ordered_with<Iterator, I>;template<different-from<basic_const_iterator> I> friend constexpr auto operator<=>(const basic_const_iterator& x, const I& y) requires random_access_iterator<Iterator> && totally_ordered_with<Iterator, I> && three_way_comparable_with<Iterator, I>;[…] template<sized_sentinel_for<Iterator> S>friendconstexpr difference_type operator-(const basic_const_iterator& x,const S& y) const; template<not-a-const-iteratorsized_sentinel_for<Iterator>S> requires sized_sentinel_fordifferent-from<S, Iteratorbasic_const_iterator> friend constexpr difference_type operator-(const S& x, const basic_const_iterator& y); }; }
Modify 25.5.3.5 [const.iterators.ops] as indicated:
[…]
template<sentinel_for<Iterator> S>friendconstexpr bool operator==(const basic_const_iterator& x,const S& s) const;-16- Effects: Equivalent to: return
x.current_ == s;friendconstexpr bool operator<(const basic_const_iterator& x,const basic_const_iterator& y) const requires random_access_iterator<Iterator>;friendconstexpr bool operator>(const basic_const_iterator& x,const basic_const_iterator& y) const requires random_access_iterator<Iterator>;friendconstexpr bool operator<=(const basic_const_iterator& x,const basic_const_iterator& y) const requires random_access_iterator<Iterator>;friendconstexpr bool operator>=(const basic_const_iterator& x,const basic_const_iterator& y) const requires random_access_iterator<Iterator>;friendconstexpr auto operator<=>(const basic_const_iterator& x,const basic_const_iterator& y) const requires random_access_iterator<Iterator> && three_way_comparable<Iterator>;-17- Let op be the operator.
-18- Effects: Equivalent to: return
x.current_ op y.current_;template<different-from<basic_const_iterator> I>friendconstexpr bool operator<(const basic_const_iterator& x,const I& y) const requires random_access_iterator<Iterator> && totally_ordered_with<Iterator, I>; template<different-from<basic_const_iterator> I>friendconstexpr bool operator>(const basic_const_iterator& x,const I& y) const requires random_access_iterator<Iterator> && totally_ordered_with<Iterator, I>; template<different-from<basic_const_iterator> I>friendconstexpr bool operator<=(const basic_const_iterator& x,const I& y) const requires random_access_iterator<Iterator> && totally_ordered_with<Iterator, I>; template<different-from<basic_const_iterator> I>friendconstexpr bool operator>=(const basic_const_iterator& x,const I& y) const requires random_access_iterator<Iterator> && totally_ordered_with<Iterator, I>; template<different-from<basic_const_iterator> I>friendconstexpr auto operator<=>(const basic_const_iterator& x,const I& y) const requires random_access_iterator<Iterator> && totally_ordered_with<Iterator, I> && three_way_comparable_with<Iterator, I>;[…]-19- Let op be the operator.
-20-
ReturnsEffects: Equivalent to: returnx.current_ op y;template<sized_sentinel_for<Iterator> S>friendconstexpr difference_type operator-(const basic_const_iterator& x,const S& y) const;-24- Effects: Equivalent to: return
x.current_ - y;template<not-a-const-iteratorsized_sentinel_for<Iterator>S> requires sized_sentinel_fordifferent-from<S, Iteratorbasic_const_iterator> friend constexpr difference_type operator-(const S& x, const basic_const_iterator& y);-25- Effects: Equivalent to: return x - y.current_;
Section: 17.3.2 [version.syn] Status: Ready Submitter: Daniel Marshall Opened: 2022-11-02 Last modified: 2022-11-12
Priority: Not Prioritized
View other active issues in [version.syn].
View all other issues in [version.syn].
View all issues with Ready status.
Discussion:
The current feature test macro is __cpp_lib_find_last which is inconsistent with almost all other ranges algorithms which are in the form __cpp_lib_ranges_xxx.
Proposed resolution is to rename the macro to __cpp_lib_ranges_find_last.[Kona 2022-11-12; Move to Ready]
Proposed resolution:
This wording is relative to N4917.
Modify 17.3.2 [version.syn], header <version> synopsis, as indicated:
[…] #define __cpp_lib_filesystem 201703L // also in <filesystem> #define __cpp_lib_ranges_find_last 202207L // also in <algorithm> #define __cpp_lib_flat_map 202207L // also in <flat_map> […]
Section: 26.7.21.1 [range.as.const.overview] Status: Ready Submitter: Tomasz Kamiński Opened: 2022-11-03 Last modified: 2022-11-10
Priority: Not Prioritized
View all issues with Ready status.
Discussion:
For v being a non-const lvalue of type std::vector<int>, views::as_const(v) produces ref_view<std::vector<int> const>. However, when v is converted to ref_view by using views::all, views::as_const(views::all(v)) produces as_const_view<ref_view<std::vector<int>>>.
Invoking views::as_const on ref_view<T> should produce ref_view<const T> when const T models a constant range. This will reduce the number of instantiations, and make a behavior of views::as_const consistent on references and ref_view to containers.[Kona 2022-11-08; Move to Ready]
Proposed resolution:
This wording is relative to N4917.
Modify 26.7.21.1 [range.as.const.overview] as indicated:
[Drafting note: If we have ref_view<V>, when V is constant propagating view (single_view, owning_view), we still can (and should) produce ref_view<V const>. This wording achieves that.]
-2- The name views::as_const denotes a range adaptor object (26.7.2 [range.adaptor.object]). Let E be an expression, let T be decltype((E)), and let U be remove_cvref_t<T>. The expression views::as_const(E) is expression-equivalent to:
(2.1) — If views::all_t<T> models constant_range, then views::all(E).
(2.2) — Otherwise, if U denotes span<X, Extent> for some type X and some extent Extent, then span<const X, Extent>(E).
(2.?) — Otherwise, if U denotes ref_view<X> for some type X and const X models constant_range, then ref_view(static_cast<const X&>(E.base())).
(2.3) — Otherwise, if E is an lvalue, const U models constant_range, and U does not model view, then ref_view(static_cast<const U&>(E)).
(2.4) — Otherwise, as_const_view(E).
Section: 21.3.5.4 [meta.unary.prop] Status: Tentatively Ready Submitter: Tim Song Opened: 2022-11-08 Last modified: 2022-11-10
Priority: Not Prioritized
View other active issues in [meta.unary.prop].
View all other issues in [meta.unary.prop].
Discussion:
The intent of P2255R2 is for the reference_meows_from_temporary traits to fully support cases where a prvalue is used as the source. Unfortunately the wording fails to do so because it tries to use the is_meowible traits to say "the initialization is well-formed", but those traits only consider initialization from xvalues, not prvalues. For example, given:
struct U { U(); U(U&&) = delete; }; struct T { T(U); };
reference_constructs_from_temporary_v<const T&, U> should be true, but is currently defined as false. We need to spell out the "is well-formed" condition directly.
[Kona 2022-11-08; Move to Tentatively Ready]
Proposed resolution:
This wording is relative to N4917.
[Drafting note: The note is already repeated every time we talk about "immediate context".]
Modify 21.3.3 [meta.type.synop], Table 46 ([tab:meta.unary.prop]) — "Type property predicates" — as indicated:
Table 46: Type property predicates [tab:meta.unary.prop] Template Condition Preconditions … template<class T, class U>
struct reference_constructs_from_temporary;conjunction_v<is_reference<T>, is_constructible<T, U>> is trueT is a reference type, and the initialization T t(VAL<U>); is well-formed and binds t to a temporary object whose lifetime is extended (6.7.7 [class.temporary]). Access checking is performed as if in a context unrelated to T and U. Only the validity of the immediate context of the variable initialization is considered. [Note ?: The initialization can result in effects such as the instantiation of class template specializations and function template specializations, the generation of implicitly-defined functions, and so on. Such effects are not in the "immediate context" and can result in the program being ill-formed. — end note]T and U shall be complete types, cv void, or arrays of unknown bound. template<class T, class U>
struct reference_converts_from_temporary;conjunction_v<is_reference<T>, is_convertible<U, T>> is trueT is a reference type, and the initialization T t = VAL<U>; is well-formed and binds t to a temporary object whose lifetime is extended (6.7.7 [class.temporary]). Access checking is performed as if in a context unrelated to T and U. Only the validity of the immediate context of the variable initialization is considered. [Note ?: The initialization can result in effects such as the instantiation of class template specializations and function template specializations, the generation of implicitly-defined functions, and so on. Such effects are not in the "immediate context" and can result in the program being ill-formed. — end note]T and U shall be complete types, cv void, or arrays of unknown bound.
Section: 26.7.31.3 [ranges.cartesian.iterator] Status: Ready Submitter: Hewill Kang Opened: 2022-11-08 Last modified: 2022-11-10
Priority: Not Prioritized
View all other issues in [ranges.cartesian.iterator].
View all issues with Ready status.
Discussion:
Currently, cartesian_product_view::iterator::prev has the following Effects:
auto& it = std::get<N>(current_); if (it == ranges::begin(std::get<N>(parent_->bases_))) { it = cartesian-common-arg-end(std::get<N>(parent_->bases_)); if constexpr (N > 0) { prev<N - 1>(); } } --it;
which decrements the underlying iterator one by one using recursion. However, when N == 0, it still detects if the first iterator has reached the beginning and assigns it to the end, which is not only unnecessary, but also causes cartesian-common-arg-end to be applied to the first range, making it ill-formed in some cases, for example:
#include <ranges>
int main() {
auto r = std::views::cartesian_product(std::views::iota(0));
r.begin() += 3; // hard error
}
This is because, for the first range, cartesian_product_view::iterator::operator+= only requires it to model random_access_range. However, when x is negative, this function will call prev and indirectly calls cartesian-common-arg-end, since the latter constrains its argument to satisfy cartesian-product-common-arg, that is, common_range<R> || (sized_range<R> && random_access_range<R>), which is not the case for the unbounded iota_view, resulting in a hard error in prev's function body.
The proposed resolution changes the position of the if constexpr so that we just decrement the first iterator and nothing else.[Kona 2022-11-08; Move to Ready]
Proposed resolution:
This wording is relative to N4917.
Modify 26.7.31.3 [ranges.cartesian.iterator] as indicated:
template<size_t N = sizeof...(Vs)> constexpr void prev();-6- Effects: Equivalent to:
auto& it = std::get<N>(current_); if constexpr (N > 0) { if (it == ranges::begin(std::get<N>(parent_->bases_))) { it = cartesian-common-arg-end(std::get<N>(parent_->bases_));if constexpr (N > 0) {prev<N - 1>();}} } --it;
Section: 22.14.6.5 [format.parse.ctx] Status: Ready Submitter: Victor Zverovich Opened: 2022-11-09 Last modified: 2022-11-11
Priority: Not Prioritized
View all issues with Ready status.
Discussion:
The definition of check_arg_id in 22.14.6.5 [format.parse.ctx] includes a (compile-time) argument id check in the Remarks element:
constexpr void check_arg_id(size_t id);[…]
Remarks: Call expressions where id >= num_args_ are not core constant expressions (7.7 [expr.const]).
However, a similar check is missing from next_arg_id which means that there is no argument id validation in user-defined format specification parsing code that invokes this function (e.g. when parsing automatically indexed dynamic width).
Previous resolution [SUPERSEDED]:
This wording is relative to N4917.
Modify 22.14.6.5 [format.parse.ctx] as indicated:
constexpr size_t next_arg_id();-7- Effects: If indexing_ != manual, equivalent to:
if (indexing_ == unknown) indexing_ = automatic; return next_arg_id_++;-8- Throws: format_error if indexing_ == manual which indicates mixing of automatic and manual argument indexing.
-?- Remarks: Call expressions where next_arg_id_ >= num_args_ are not core constant expressions (7.7 [expr.const]).
[2022-11-11; Tomasz provide improved wording; Move to Open]
Clarify that the value of next_arg_id_ is used, and add missing "is true."
[Kona 2022-11-11; move to Ready]
Proposed resolution:
This wording is relative to N4917.
Modify 22.14.6.5 [format.parse.ctx] as indicated:
constexpr size_t next_arg_id();-7- Effects: If indexing_ != manual is true, equivalent to:
if (indexing_ == unknown) indexing_ = automatic; return next_arg_id_++;-8- Throws: format_error if indexing_ == manual is true which indicates mixing of automatic and manual argument indexing.
-?- Remarks: Let cur-arg-id be the value of next_arg_id_ prior to this call. Call expressions where cur-arg-id >= num_args_ is true are not core constant expressions (7.7 [expr.const]).
constexpr size_t check_arg_id(size_t id);-9- Effects: If indexing_ != automatic is true, equivalent to:
if (indexing_ == unknown) indexing_ = manual;-10- Throws: format_error if indexing_ == automatic is true which indicates mixing of automatic and manual argument indexing.
-11- Remarks: Call expressions where id >= num_args_ is true are not core constant expressions (7.7 [expr.const]).