P3059R2: Making user-defined constructors of view iterators/sentinels private. - #6382
P3059R2: Making user-defined constructors of view iterators/sentinels private. #6382iskandarem wants to merge 25 commits into
Conversation
…e filter_view a friend class to filter_view::iterator/sentinel classes;
…ream_view is friend class now, cause it has to have the access to the private-constructor; test written for that;
…sponding tests are written;
… tests are written;
…friend lazy_split_view added for lazy_split_view to have access
…ew made friend class for sentinel; corresponding tests has been written;
…ng tests are written;
This comment was marked as resolved.
This comment was marked as resolved.
There was a problem hiding this comment.
Pull request overview
Implements P3059R2 by making internal view iterator and sentinel constructors private while preserving required public operations.
Changes:
- Restricts iterator and sentinel construction across affected C++20 views.
- Adds compile-time tests for constructor accessibility and public interfaces.
- Registers the new tests in the STL test suite.
Reviewed changes
Copilot reviewed 23 out of 23 changed files in this pull request and generated 13 comments.
Show a summary per file
| File | Description |
|---|---|
stl/inc/ranges |
Restricts constructors for affected range views. |
stl/inc/__msvc_ranges_to.hpp |
Restricts transform_view constructors. |
tests/std/test.lst |
Registers the new tests. |
tests/std/tests/P3059R2_basic_istream_view_iterator_constructor/test.compile.pass.cpp |
Tests basic_istream_view. |
tests/std/tests/P3059R2_basic_istream_view_iterator_constructor/env.lst |
Configures the test matrix. |
tests/std/tests/P3059R2_elements_view_iterator_sentinel_constructors/test.compile.pass.cpp |
Tests elements_view. |
tests/std/tests/P3059R2_elements_view_iterator_sentinel_constructors/env.lst |
Configures the test matrix. |
tests/std/tests/P3059R2_filter_view_iterator_sentinel_constructors/test.compile.pass.cpp |
Tests filter_view. |
tests/std/tests/P3059R2_filter_view_iterator_sentinel_constructors/env.lst |
Configures the test matrix. |
tests/std/tests/P3059R2_iota_view_iterator_sentinel_construtors/test.compile.pass.cpp |
Tests iota_view. |
tests/std/tests/P3059R2_iota_view_iterator_sentinel_construtors/env.lst |
Configures the test matrix. |
tests/std/tests/P3059R2_join_view_sentinel_constructor/test.compile.pass.cpp |
Tests join_view. |
tests/std/tests/P3059R2_join_view_sentinel_constructor/env.lst |
Configures the test matrix. |
tests/std/tests/P3059R2_lazy_split_view_inner_outer_iterators_constructors/test.compile.pass.cpp |
Tests lazy_split_view. |
tests/std/tests/P3059R2_lazy_split_view_inner_outer_iterators_constructors/env.lst |
Configures the test matrix. |
tests/std/tests/P3059R2_split_view_iterator_sentinel_constructors/test.compile.pass.cpp |
Tests split_view. |
tests/std/tests/P3059R2_split_view_iterator_sentinel_constructors/env.lst |
Configures the test matrix. |
tests/std/tests/P3059R2_take_view_sentinel_constructor/test.compile.pass.cpp |
Tests take_view. |
tests/std/tests/P3059R2_take_view_sentinel_constructor/env.lst |
Configures the test matrix. |
tests/std/tests/P3059R2_take_while_view_sentinel_constructor/test.compile.pass.cpp |
Tests take_while_view. |
tests/std/tests/P3059R2_take_while_view_sentinel_constructor/env.lst |
Configures the test matrix. |
tests/std/tests/P3059R2_transform_view_iterator_sentinel_constructors/test.compile.pass.cpp |
Tests transform_view. |
tests/std/tests/P3059R2_transform_view_iterator_sentinel_constructors/env.lst |
Configures the test matrix. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| } | ||
| } | ||
|
|
||
| friend lazy_split_view; |
| # Copyright (c) Microsoft Corporation. | ||
| # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | ||
|
|
||
| RUNALL_INCLUDE ..\usual_latest_matrix.lst |
| # Copyright (c) Microsoft Corporation. | ||
| # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | ||
|
|
||
| RUNALL_INCLUDE ..\usual_latest_matrix.lst |
| # Copyright (c) Microsoft Corporation. | ||
| # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | ||
|
|
||
| RUNALL_INCLUDE ..\usual_latest_matrix.lst |
| # Copyright (c) Microsoft Corporation. | ||
| # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | ||
|
|
||
| RUNALL_INCLUDE ..\usual_latest_matrix.lst No newline at end of file |
| @@ -0,0 +1 @@ | |||
| RUNALL_INCLUDE ..\usual_latest_matrix.lst No newline at end of file | |||
| // basic_istream_view::iterator directly from the parent view. | ||
| static_assert(!constructible_from<Iterator, View&>); | ||
|
|
||
| // The iterator must remain copyable and movable through its public interface. |
| tests\P3059R2_basic_istream_view_iterator_constructor | ||
| tests\P3059R2_elements_view_iterator_sentinel_constructors | ||
| tests\P3059R2_filter_view_iterator_sentinel_constructors | ||
| tests\P3059R2_iota_view_iterator_sentinel_construtors |
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
|
Please don't just click "Commit suggestion" on Copilot code review comments. That will immediately push a commit, rerunning checks. (I don't personally pay for them, but they're a shared resource, and if we waste it enough, someone will eventually hiss at me.) In this case, there were several code review comments from Copilot, and your checks were already failing in Code Format Validation. The proper thing to do is to locally address all feedback, from both the failed checks and Copilot, then validate and push a single time. (You can push one or multiple commits at once.) |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 23 out of 23 changed files in this pull request and generated 1 comment.
Comments suppressed due to low confidence (11)
tests/std/tests/P3059R2_basic_istream_view_iterator_constructor/test.compile.pass.cpp:17
- This comment contradicts the assertion immediately below: the iterator is intentionally not copy-constructible. Describe the preserved move-only interface instead.
// The iterator must remain copyable and movable through its public interface.
tests/std/test.lst:742
- The new test directory misspells “constructors” as “construtors”. Rename the directory and update this registration to keep the test name consistent with the other P3059R2 tests.
tests\P3059R2_iota_view_iterator_sentinel_construtors
tests/std/tests/P3059R2_transform_view_iterator_sentinel_constructors/env.lst:4
- P3059R2 was adopted as a defect report retroactive to C++20, but
usual_latest_matrix.lstonly compiles this test with/std:c++latest. Use the C++20 matrix so the required C++20 behavior is covered as well.
RUNALL_INCLUDE ..\usual_latest_matrix.lst
tests/std/tests/P3059R2_take_view_sentinel_constructor/env.lst:4
- P3059R2 was adopted as a defect report retroactive to C++20, but
usual_latest_matrix.lstonly compiles this test with/std:c++latest. Use the C++20 matrix so the required C++20 behavior is covered as well.
RUNALL_INCLUDE ..\usual_latest_matrix.lst
tests/std/tests/P3059R2_split_view_iterator_sentinel_constructors/env.lst:4
- P3059R2 was adopted as a defect report retroactive to C++20, but
usual_latest_matrix.lstonly compiles this test with/std:c++latest. Use the C++20 matrix so the required C++20 behavior is covered as well.
RUNALL_INCLUDE ..\usual_latest_matrix.lst
tests/std/tests/P3059R2_lazy_split_view_inner_outer_iterators_constructors/env.lst:4
- P3059R2 was adopted as a defect report retroactive to C++20, but
usual_latest_matrix.lstonly compiles this test with/std:c++latest. Use the C++20 matrix so the required C++20 behavior is covered as well.
RUNALL_INCLUDE ..\usual_latest_matrix.lst
tests/std/tests/P3059R2_join_view_sentinel_constructor/env.lst:4
- P3059R2 was adopted as a defect report retroactive to C++20, but
usual_latest_matrix.lstonly compiles this test with/std:c++latest. Use the C++20 matrix so the required C++20 behavior is covered as well.
RUNALL_INCLUDE ..\usual_latest_matrix.lst
tests/std/tests/P3059R2_iota_view_iterator_sentinel_construtors/env.lst:4
- P3059R2 was adopted as a defect report retroactive to C++20, but
usual_latest_matrix.lstonly compiles this test with/std:c++latest. Use the C++20 matrix so the required C++20 behavior is covered as well.
RUNALL_INCLUDE ..\usual_latest_matrix.lst
tests/std/tests/P3059R2_filter_view_iterator_sentinel_constructors/env.lst:1
- P3059R2 was adopted as a defect report retroactive to C++20, but
usual_latest_matrix.lstonly compiles this test with/std:c++latest. Use the C++20 matrix so the required C++20 behavior is covered as well.
RUNALL_INCLUDE ..\usual_latest_matrix.lst
tests/std/tests/P3059R2_elements_view_iterator_sentinel_constructors/env.lst:4
- P3059R2 was adopted as a defect report retroactive to C++20, but
usual_latest_matrix.lstonly compiles this test with/std:c++latest. Use the C++20 matrix so the required C++20 behavior is covered as well.
RUNALL_INCLUDE ..\usual_latest_matrix.lst
tests/std/tests/P3059R2_basic_istream_view_iterator_constructor/env.lst:1
- P3059R2 was adopted as a defect report retroactive to C++20, but
usual_latest_matrix.lstonly compiles this test with/std:c++latest. Use the C++20 matrix so the required C++20 behavior is covered as well.
RUNALL_INCLUDE ..\usual_latest_matrix.lst
| _Iterator() | ||
| requires default_initializable<iterator_t<_Vw>> | ||
| = default; | ||
| friend filter_view; // to so that filter_view had access to the private constructor |
There was a problem hiding this comment.
I agree with Copilot here (except that it wants to mess up the indentation, lol). In general, inspect similar code and see if we think something is subtle enough to deserve a comment.
Sorry, I didn't know about that. I'll pay attention in the future. |
|
No worries, I still need to write up Contribution Guidelines that will explain all of this stuff. While your PR is in draft state, pushes won't trigger checks. When you want checks to run again, move your PR back to ready to review, then push commits. If you do the reverse (push then move to ready), the state change of moving out of draft mode won't trigger checks. |
|
Azure Pipelines: Successfully started running 1 pipeline(s). There may be pipelines that require an authorized user to comment /azp run to run. |
| // const sentinel specialization must remain available. | ||
| static_assert(constructible_from<ConstSentinel, Sentinel>); | ||
|
|
||
| // A non-common underlying range causes take_view to use its sentinel type. |
| static_assert(constructible_from<ConstSentinel, Sentinel>); | ||
|
|
||
|
|
||
| // The outer range is non-common, causing join_view to use its sentinel type. |
| static_assert(test_non_common_filter_view()); | ||
|
|
||
|
|
||
| // Use a non-common underlying range so filter_view has a distinct sentinel. |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 23 out of 23 changed files in this pull request and generated 3 comments.
Comments suppressed due to low confidence (2)
tests/std/tests/P3059R2_take_view_sentinel_constructor/test.compile.pass.cpp:50
Baseis a simple view, so the non-consttake_viewoverload is disabled and both aliases resolve to_Sentinel<true>. This assertion therefore checks the copy constructor rather than the required<false>-to-<true>conversion, and_Sentinel<false>is never exercised. Use a non-simple base with distinct const/non-const range types and assert thatSentinelandConstSentineldiffer before checking conversion.
// The public conversion from the non-const sentinel specialization to the
// const sentinel specialization must remain available.
static_assert(constructible_from<ConstSentinel, Sentinel>);
tests/std/tests/P3059R2_join_view_sentinel_constructor/test.compile.pass.cpp:50
- The chosen
subrangeis a simple view, andjoin_view::end()therefore already returns_Sentinel<true>for non-constView.SentinelandConstSentinelare identical, so this assertion tests copying instead of the converting constructor and never covers_Sentinel<false>. Use a non-simple outer range and assert the two aliases differ before this check.
// The public conversion from sentinel<false> to sentinel<true> must remain
// available.
static_assert(constructible_from<ConstSentinel, Sentinel>);
| // The public converting constructor from sentinel<false> to sentinel<true> | ||
| // must remain available. | ||
| static_assert(constructible_from<ConstSentinel, Sentinel>); |
| // The public conversion from outer-iterator<false> to outer-iterator<true> | ||
| // remains available. | ||
| static_assert(constructible_from<ConstForwardOuter, ForwardOuter>); |
| // The public non-const-to-const converting constructors must remain | ||
| // available. | ||
| static_assert(constructible_from<ConstIterator, Iterator>); | ||
| static_assert(constructible_from<ConstSentinel, Sentinel>); |
| /* [[no_unique_address]] */ _Bo _Last{}; | ||
|
|
||
| constexpr explicit _Iotinel(_Bo _Last_) noexcept(is_nothrow_move_constructible_v<_Bo>) | ||
| : _Last(_STD move(_Last_)) {} |
There was a problem hiding this comment.
Currently this PR changes _Iotinel from aggregate to non-aggregate, which may cause ABI change for certain underlying bound types. Is this acceptable?
There was a problem hiding this comment.
Yes, you're right, @frederick-vs-ja. However, in the paper I found the following example:
namespace std::ranges {
template<weakly_incrementable W, semiregular Bound>
requires weakly-equality-comparable-with<W, Bound> && copyable<W>
struct iota_view<W, Bound>::iterator {
private:
W value_ = W(); // exposition only
constexpr explicit iterator(W value); // exposition only
public:
// ...
iterator() requires default_initializable<W> = default;
// ...
};
}Based on that wording, I thought the closest implementation would be to make the stored bound and the constructor private, matching the paper's exposition as closely as possible.
I'd also be interested to know what the MSVC STL policy is regarding ABI changes in cases like this
There was a problem hiding this comment.
The C++23 ABI is not yet finalized so we can change it arbitrarily.
The C++20 ABI is finalized so we need to avoid breaking it. That said, if this is a Standard-mandated change, and restricted to iota_view sentinels, I have difficulty imagining them being passed across TUs, so we could probably get away with a change. I would like a complete accounting of which types are changing from aggregate to non-aggregate. It may be safer to rename any such types with a 2 suffix, e.g. _Iotinel2, which could limit the blast radius.
There was a problem hiding this comment.
I would like a complete accounting of which types are changing from aggregate to non-aggregate.
I checked this before and only found _Iotinel. Other iterator and sentinel types were non-aggregate initially.
I considered modifying _Iotinel as the following
struct _Iotinel {
struct _Layer {
/* [[no_unique_address]] */ _Bo _Last{};
};
// omitting other members
};, which can keep _Iotinel an aggregate and avoid changing size and alignment (IIUC), while making is_constructible_v<_Iotinel, _Bo> false. Although this method still allows users to construct _Iotinel from _Bo using list-initialization.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 23 out of 23 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (5)
tests/std/tests/P3059R2_take_view_sentinel_constructor/test.compile.pass.cpp:50
Baseis a simple view, so the non-constend()overload is disabled and bothSentinelandConstSentinelname_Sentinel<true>. This assertion therefore checks ordinary copy construction, leaving the public<false>-to-<true>conversion and the private constructor on<false>untested. Use a non-simple base and first assert that the two aliases differ.
static_assert(constructible_from<ConstSentinel, Sentinel>);
tests/std/tests/P3059R2_take_while_view_sentinel_constructor/test.compile.pass.cpp:54
- Because
Baseis a simple view,ranges::sentinel_t<View>uses the constend()overload; consequentlySentinelandConstSentinelare both_Sentinel<true>. This only verifies copying and leaves the public<false>-to-<true>conversion and the private constructor on<false>untested. Use a non-simple base and first assert that the two sentinel aliases are distinct.
static_assert(constructible_from<ConstSentinel, Sentinel>);
tests/std/tests/P3059R2_join_view_sentinel_constructor/test.compile.pass.cpp:50
Baseis a simple view, so non-constjoin_view::end()also returns_Sentinel<true>and this assertion tests its copy constructor rather than the<false>-to-<true>conversion. The private constructor on_Sentinel<false>is therefore uncovered. Use a non-simple outer range and assert thatSentinelandConstSentineldiffer.
static_assert(constructible_from<ConstSentinel, Sentinel>);
tests/std/tests/P3059R2_lazy_split_view_inner_outer_iterators_constructors/test.compile.pass.cpp:61
string_viewandsingle_vieware both simple views, soForwardView::begin()selects_Outer_iter<true>even on a non-const view. ThusForwardOuterequalsConstForwardOuter, this assertion only checks the copy constructor, and the forward-range_Outer_iter<false>constructor and conversion remain untested. Use a non-simple forward base and assert the aliases differ before checking conversion.
static_assert(constructible_from<ConstForwardOuter, ForwardOuter>);
tests/std/tests/P3059R2_elements_view_iterator_sentinel_constructors/test.compile.pass.cpp:77
- Since
Baseis a simple view, the non-constelements_viewoverloads are disabled andIterator/Sentinelalready name the<true>specializations. Both assertions therefore test copy construction, leaving the<false>constructors and the intended non-const-to-const conversions uncovered. Use a non-simple base and assert that each non-const alias differs from its const counterpart.
static_assert(constructible_from<ConstIterator, Iterator>);
static_assert(constructible_from<ConstSentinel, Sentinel>);
|
Some tests check if the views internal constructors of iterator/sentinel are accessible to users which this paper makes private. Example: void test_constructor_wrong_range() {
vector<int> vec0{0, 1, 2, 3};
vector<int> vec1{4, 5, 6, 7};
auto r0 = views::filter(vec0, lambda);
using R = decltype(r0);
same_as<R> auto r1 = views::filter(vec1, lambda);
ranges::iterator_t<R> i{r0, r1.begin().base()}; // vector iterators in range are from different containers
}Should we remove these tests or should we just adjust them to right behavior? |
|
If such tests can't be exercised from user code (without granting friendship) then they should simply be removed, as the avenues for misuse have been closed off. (We should possibly consider removing same-parent checks from now-private constructors, or make them STL-internal checks.) |
…o unable-view-sentinel-iterator-creation-from-outside
|
Azure Pipelines: Successfully started running 1 pipeline(s). There may be pipelines that require an authorized user to comment /azp run to run. |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 28 out of 28 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (5)
tests/std/tests/P3059R2_take_view_sentinel_constructor/test.compile.pass.cpp:50
Baseis a simple view, so the non-consttake_viewoverloads are disabled and bothSentinelandConstSentineldenote_Sentinel<true>. This assertion therefore checks ordinary copy construction, not the intended<false>-to-<true>conversion; the private constructor of_Sentinel<false>is also never tested. Please use a non-simple underlying view and assert that the two sentinel types differ before checking the conversion.
// The public conversion from the non-const sentinel specialization to the
// const sentinel specialization must remain available.
static_assert(constructible_from<ConstSentinel, Sentinel>);
tests/std/tests/P3059R2_take_while_view_sentinel_constructor/test.compile.pass.cpp:54
Baseis a simple view, sotake_while_viewexposes_Sentinel<true>even through a non-constView; consequentlySentinelandConstSentinelare the same type and this only tests the copy constructor. It also leaves the private constructor of_Sentinel<false>untested. Please use a non-simple underlying view and verify the types differ before asserting the conversion.
// The public converting constructor from sentinel<false> to sentinel<true>
// must remain available.
static_assert(constructible_from<ConstSentinel, Sentinel>);
tests/std/tests/P3059R2_join_view_sentinel_constructor/test.compile.pass.cpp:50
- Because this
subrangeis a simple view, non-constjoin_view::end()already returns_Sentinel<true>. ThusSentinelequalsConstSentinel, making this a copy-construction check and leaving both the<false>constructor privacy and the<false>-to-<true>conversion uncovered. Use a non-simple outer view and assert the sentinel types are distinct.
// The public conversion from sentinel<false> to sentinel<true> must remain
// available.
static_assert(constructible_from<ConstSentinel, Sentinel>);
tests/std/tests/P3059R2_lazy_split_view_inner_outer_iterators_constructors/test.compile.pass.cpp:61
ForwardBaseandPatternare both simple views, soForwardView::begin()usesouter-iterator<true>even for a non-const view.ForwardOutertherefore equalsConstForwardOuter, and this assertion only exercises copying rather than the public<false>-to-<true>conversion it claims to test. Please use a non-simple forward base and assert the iterator types differ.
// The public conversion from outer-iterator<false> to outer-iterator<true>
// remains available.
static_assert(constructible_from<ConstForwardOuter, ForwardOuter>);
tests/std/tests/P3059R2_elements_view_iterator_sentinel_constructors/test.compile.pass.cpp:77
Baseis a simple view, which disables the non-constelements_viewoverloads;Iterator/Sentineltherefore already denote the same<true>specializations as theirConstaliases. These assertions only test copy construction, while the<false>implementation-only constructors and the intended<false>-to-<true>conversions remain uncovered. Use a non-simple base and first assert that each pair of types differs.
// The public non-const-to-const converting constructors must remain
// available.
static_assert(constructible_from<ConstIterator, Iterator>);
static_assert(constructible_from<ConstSentinel, Sentinel>);
closes #6241