C++ Logo

std-proposals

Advanced search

Re: [std-proposals] Defaulted operator++(int) and operator--(int)

From: Artur z Redy <arturbac.ab_at_[hidden]>
Date: Tue, 3 Dec 2024 18:32:44 +0100
I was not precise, I don't mind this proposed change at all [with
explicit declaration of operator++(int) = default;]
It doesn't obstruct from my knowledge anything.
I just wanted to point out that the motivation for this proposal has a
source in requirement for iterators to define this operator even when
they are unused like in views just because of
std::input_or_output_iterator requirements.

On Tue, Dec 3, 2024 at 5:09 PM Arthur O'Dwyer <arthur.j.odwyer_at_[hidden]> wrote:
>
> On Tue, Dec 3, 2024 at 8:08 AM Artur z Redy via Std-Proposals <std-proposals_at_[hidden]> wrote:
>>
>> > You can write `operator!=(...) = default`, but no C++20 programmer ever would write it, because we have a rewrite rule that handles that transparently, without the programmer's intervention. So, it seems like it would make sense to handle postfix `++` in the same way, right?"
>>
>> The question is why would anyone want to implement this operator ++(int)
>> [...]
>> So for example for implementing a view that would be a forward range,
>> the iterator must be a forward_iterator and have operator++(int) [...]
>> But when dealing with such complex iterators, a copy of an iterator
>> may cost a lot (like in my example).
>> So I usually declare them only and leave them as unresolved symbols,
>> that way I know code has optimal performance and this inefficient
>> operator is unused at all.
>
>
> That seems like a good approach for your use-case, yeah. You could even declare-but-not-define the copy constructor itself!
> With the rewrite-rule approach to `operator++`, we would have something like this:
>
> // This is the only case where the proposed change has any effect
> struct It1 {
> It1& operator++() { ~~~~ }
> ~~~~
> };
> static_assert(std::forward_iterator<It1>);
> int main() { ++it; it++; } // OK, it++ is implicitly rewritten to `[&](){ auto c = it; ++it; return c; }()`
>
> // This is your use-case, unchanged by the proposal
> struct It2 {
> It2& operator++() { ~~~~ }
> It2& operator++(int); // declared but not defined
> ~~~~
> };
> static_assert(std::forward_iterator<It2>);
> int main() { ++it; it++; } // compiles, but does not link because it++ is never defined
>
> // This is how you'd "opt out" of case #1, also happens to be legal today
> struct It3 {
> It3& operator++() { ~~~~ }
> It3& operator++(int) = delete;
> ~~~~
> };
> static_assert(not std::forward_iterator<It3>); // because it++ is not well-formed
> int main() { ++it; it++; } // does not compile because it++ is deleted
>
>
>> Maybe there is in future standards a way to opt out "operator ++(int)
>> = delete" and still have a valid forward iterator in view that meets
>> forard_range requirements ..
>
>
> Well, every forward_iterator must be copyable. So it must be possible to do the-same-effect-as `it++`, manually:
> auto copy = it;
> ++it;
> use(copy);
> And C++ gives us an actual syntax for that particular semantic: it's:
> use(it++);
> So any iterator that supports copying should also support `it++` — not to do so would be just perversely cumbersome for the iterator's user.
>
> It's true that nobody should use `it++` in an algorithm that could just as well make do with `++it`. But that's nothing new. Nobody should use `--it` in an algorithm where they mean `++it`, either. We can't stop people from just literally writing their algorithm incorrectly, nor inefficiently. To the extent that the paper standard itself cares about efficiency, we express efficiency requirements via "Complexity" elements in the library clauses. A third-party library could do the same (e.g. say "This algorithm requires a forward iterator, but I promise I won't copy it any more than absolutely necessary"). A third-party library that never needs to copy the iterator should probably just document/constrain itself to accept input_iterator in the first place.
>
> –Arthur

Received on 2024-12-03 17:32:57