C++ Logo

std-proposals

Advanced search

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

From: Arthur O'Dwyer <arthur.j.odwyer_at_[hidden]>
Date: Tue, 3 Dec 2024 11:08:56 -0500
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 16:09:12