feature/spaceship: Clause 23: Iterators#1645
feature/spaceship: Clause 23: Iterators#1645StephanTLavavej merged 1 commit intomicrosoft:feature/spaceshipfrom
Conversation
miscco
left a comment
There was a problem hiding this comment.
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 |
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_iteratorandistreambuf_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_tWG21-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_tby value while preserving our compiler throughput workaround for MSVC permissive mode - not worth it.(2)
common_iteratorWG21-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.