Date: Tue, 29 Apr 2025 15:17:02 -0400
On Tue, Apr 29, 2025 at 3:06 PM Zhihao Yuan via Std-Proposals <
std-proposals_at_[hidden]> wrote:
> On Tuesday, April 29th, 2025 at 3:43 AM, Aurelien Cassagnes via
> Std-Proposals <std-proposals_at_[hidden]> wrote:
>
> There is a fairly arbitrary (unless I missed an important argument)
> limitation on initialization clause for if statements.
>
> - I have that
> if (auto it = vec.begin(); it != vec.end()) {
>
> - I want this
> if (auto it = vec.begin() /*and*/ auto itt = ++vec.begin(); it !=
> vec.end() && it != itt) {
>
> So you end up using one of the workarounds, which are somewhat hacky imo :/
>
>
> The particular example can use multi-declarator in
> the declaration as
> https://lists.isocpp.org/std-proposals/2025/04/13529.php
> showed. If the two initializers can deduce to
> different types, then
>
> if (auto [it, itt] = std::tuple{ v1.begin(), std::next(v1.begin()) };
> it != v1.end() && itt != v1.end()) {
>
> 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;
[...]
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?
Cheers,
Arthur
std-proposals_at_[hidden]> wrote:
> On Tuesday, April 29th, 2025 at 3:43 AM, Aurelien Cassagnes via
> Std-Proposals <std-proposals_at_[hidden]> wrote:
>
> There is a fairly arbitrary (unless I missed an important argument)
> limitation on initialization clause for if statements.
>
> - I have that
> if (auto it = vec.begin(); it != vec.end()) {
>
> - I want this
> if (auto it = vec.begin() /*and*/ auto itt = ++vec.begin(); it !=
> vec.end() && it != itt) {
>
> So you end up using one of the workarounds, which are somewhat hacky imo :/
>
>
> The particular example can use multi-declarator in
> the declaration as
> https://lists.isocpp.org/std-proposals/2025/04/13529.php
> showed. If the two initializers can deduce to
> different types, then
>
> if (auto [it, itt] = std::tuple{ v1.begin(), std::next(v1.begin()) };
> it != v1.end() && itt != v1.end()) {
>
> 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;
[...]
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?
Cheers,
Arthur
Received on 2025-04-29 19:17:15