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;
[...]