-
Notifications
You must be signed in to change notification settings - Fork 1.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feature/spaceship: Clause 23: Iterators #1645
feature/spaceship: Clause 23: Iterators #1645
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unsurprising I could not even find a nitpick
This is an idiomatic transformation when working with concept-constrained function templates. When two overloads are identical modulo constraints, and one set of constraints subsumes the other, we can merge those overloads unobservably. Assume we have: template <class T>
requires A<T>
void f(const T&) { stuff(); }
template <class T>
requires A<T> && B<T>
void f(const T&) { other_stuff(); } for example. These are equivalent to: template <class T>
requires A<T>
void f(const T&) {
if constexpr (B<T>) {
other_stuff();
} else {
stuff();
}
} since in both cases we:
This is a huge boon to throughput since subsumption checking, as would be necessary for overload resolution in the original case when both template <class T>
requires A<T>
void f(const T&) noexcept(noexcept(stuff())) { stuff(); }
template <class T>
requires A<T> && B<T>
void f(const T&) noexcept(noexcept(other_stuff())) { other_stuff(); } things get ugly. We must similarly merge the conditional template <class T>
requires A<T>
void f(const T&) noexcept(B<T> ? noexcept(other_stuff()) : noexcept(stuff())) {
if constexpr (B<T>) {
other_stuff();
} else {
stuff();
}
} but it's frequently the case that There were a great many occurrences of overload sets like this in the Ranges TS wording since the TS was based on C++14 which predates the introduction of |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good.
Not sure, if this is relevant/helpful, but I don't think you need partial template specializations. You can just write
I think you could even write:
And avoid the separate variable altogether, but I'm not sure,if that is something desirable. |
The equivalent partially-specialized variable template is both slightly more terse: template<class T>
constexpr bool _F_is_noexcept = noexcept(stuff<T>());
template<B T>
constexpr bool _F_is_noexcept<T> = noexcept(other_stuff<T>()); and slightly better for compiler throughput since it avoids using the constexpr interpreter.
This was one of the stops I made on my trip to developing the style used for customization point objects in the STL, which have much more complicated decision trees. There are effectively three different points in a function template that need to express roughly the same decision tree: (1) the constraints on the template itself when determining which arguments to accept, (2) the dispatch mechanism in the actual function body, and (3) the conditional template <class.. Args, auto Determination = decision_tree<Args...>()>
requires Determination.strategy != None
decltype(auto) f(/* ... */) noexcept(Determination.is_noexcept) {
if constexpr (Determination.strategy == meow) {
// ...
} else if constexpr (Determination.strategy == woof) {
// ...
}
}
(Thanks for coming to my TED talk!) |
Works towards #62.
@CaseyCarter and I audited WG21-P1614's changes to Clause 23 Iterators, and found that everything was already implemented (in
main
, not justfeature/spaceship
) with the exception ofoperator!=
removal foristream_iterator
andistreambuf_iterator
. I've implemented those removals for C++20 mode.In general, we believe that
operator!=
removal doesn't merit the addition of test coverage - we should already have been testing!=
, and now the compiler will rewrite it for us. In this case, we need to skip a libcxx test, as they were directly callingstd::operator!=
which this C++20 feature intentionally makes into an error.Two notes from our investigation:
(1)
unreachable_sentinel_t
WG21-N4878 [unreachable.sentinel]/2 depicts a by-value parameter:
while we implement a by-reference parameter:
STL/stl/inc/xutility
Line 4014 in 7f08bb3
The difference is essentially unobservable, and a significant restructuring would be required in order to take
unreachable_sentinel_t
by value while preserving our compiler throughput workaround for MSVC permissive mode - not worth it.(2)
common_iterator
WG21-N4878 [common.iter.cmp] depicts two overloads:
while we implement one overload:
STL/stl/inc/iterator
Lines 937 to 964 in 7f08bb3
This is conformant, as the
if constexpr (equality_comparable_with<_Iter, _OIter>)
implements the behavior of the two-overload set, with far less code repetition and compiler throughput impact.