On Tuesday, April 29th, 2025 at 12:17 PM, Arthur O'Dwyer <arthur.j.odwyer@gmail.com> wrote:
On Tue, Apr 29, 2025 at 3:06 PM Zhihao Yuan via Std-Proposals <std-proposals@lists.isocpp.org> wrote:

To push this idea further, you can do this in C++26

template <class It1, class It2, class It3 = decltype(std::next(std::declval<It1>()))> struct first_last_next
{
It1 first;
It2 last;
It3 next = std::next(first);

explicit constexpr operator bool() const noexcept
{
return first != last and first != next;
}
};

if (auto [it, _, itt] = first_last_next{v1.begin(), v1.end()}) {

(* just a contrived snippet to show the possibility;
std::next doesn't return an iterator of a different type. *)

Wellll... IIUC, the conversion-to-boolean will happen before the destructuring access to `__temp.next` (good!), but the initialization of `__temp`, including `__temp.next`, will still happen before that. So you end up evaluating `std::next(first)` even when `first == last`, and thus you have UB for e.g. vector iterators.
IIUC, the above is essentially equivalent to:

auto first = v1.begin();
auto last = v1.end();
auto next = std::next(first); // oops, UB!
if (first != last && first != next) {
auto& it = first;
auto& itt = next;
[...]

That is the top-of-the-thread's intent,

- I want this
if (auto it = vec.begin() /*and*/ auto itt = ++vec.begin(); it != vec.end() && it != itt) {


so I did not change that.

To make this tricky code actually work, you'd have to make the call to `std::next` happen inside `get<2>(__temp)` rather than inside the initialization. Or, I guess, just make the initializer
It3 next = (first == last) ? first : std::next(first);

Right?

I need a legitimate and complete example to
know whether a transformation is equivalent 🤷

--
Zhihao Yuan, ID lichray
The best way to predict the future is to invent it.
_______________________________________________