Date: Fri, 14 Aug 2026 13:53:56 +0800
---------- Forwarded message ---------
发件人: Connor Song <perdixky_at_[hidden]>
Date: 2026年8月13日周四 21:45
Subject: Re: [std-proposals] Generalizing `_` as a non-persistent binding
To: Simon Schröder <dr.simon.schroeder_at_[hidden]>
Hi Simon,
Yes, I am aware of P2169 and the C++26 rules for name-independent _
declarations.
So far, I have not found a case where this proposal would change the
meaning of an existing well-formed program.
My current view is that this is mostly an extension of the existing _
model: it gives well-defined semantics to some cases that are currently
ill-formed, rather than replacing existing behavior.
Of course, I may still be missing a compatibility corner case, and I would
be interested in any concrete examples.
Best,
Connor
Simon Schröder <dr.simon.schroeder_at_[hidden]> 于2026年8月13日周四 20:52写道:
> Are you aware of the existing rules for _ in C++26? Here is the original
> proposal:
> https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2023/p2169r4.pdf
>
> If you want to change/extend the behavior, it has to be compatible with
> the current standard (backwards compatibility).
>
> On Thu, Aug 13, 2026 at 2:32 PM Connor Song via Std-Proposals <
> std-proposals_at_[hidden]> wrote:
>
>> Hi all,
>>
>> I would like to propose a different way of thinking about _.
>>
>> I don't think _ should mean merely:
>>
>> this variable is unused.
>>
>> A more general interpretation might be:
>>
>> this entity does not have an identity that the program is expected to
>> remember.
>>
>> Or, less formally:
>>
>> I don't need to remember this entity, so I don't need to give it a
>> persistent identifier.
>>
>> I think this interpretation naturally covers two different cases which
>> are currently treated as somewhat separate concepts.
>>
>> The first case is the familiar one: the entity is not important and can
>> simply be ignored.
>>
>> For example:
>>
>> auto [result, _] = resolve();
>>
>> Here _ means that the second result exists, but its identity is
>> irrelevant to the rest of the program.
>>
>> But there is another case:
>>
>> The entity is important briefly, but its identity should not remain
>> available indefinitely.
>>
>> For example:
>>
>> Socket _ = connect();
>>
>> TlsConnection secure(_);
>>
>> Widget _ = make_widget();
>>
>> secure.send(data);
>>
>> Suppose TlsConnection borrows the Socket.
>>
>> The socket therefore cannot simply be destroyed after constructing secure;
>> it must remain alive according to the normal C++ lifetime rules.
>>
>> However, after constructing secure, there may be no reason for later
>> code to retain a stable name for the underlying socket.
>>
>> Writing:
>>
>> Socket raw = connect();
>> TlsConnection secure(raw);
>>
>> gives the underlying socket a persistent identity, and later code can
>> accidentally use it:
>>
>> raw.send(data); // accidentally bypasses TLS
>> secure.send(data);
>>
>> Using _ expresses something different:
>>
>> Socket _ = connect();
>> TlsConnection secure(_);
>>
>> The object is needed, and the binding is temporarily needed, but the
>> identity is not something later code is expected to remember.
>>
>> This suggests extending the existing name-independent _ model.
>>
>> Conceptually, I have the following rule in mind:
>>
>> Multiple name-independent declarations named _ may inhabit the same
>> scope. An id-expression _ denotes the most recently preceding such
>> declaration.
>>
>> The previous objects would not be destroyed when a new _ is declared.
>> Their lifetimes would remain completely unchanged.
>>
>> Only the binding denoted by _ changes.
>>
>> For example:
>>
>> Socket _ = connect(); // object A
>> TlsConnection secure(_); // refers to A
>>
>> Widget _ = make_widget(); // object B, `_` now denotes B
>>
>> Object A remains alive as usual. It merely no longer has the current _
>> binding.
>>
>> There is one additional rule I think is necessary:
>>
>> A new _ binding becomes the current _ only after its initializer has
>> been evaluated.
>>
>> This permits the previous ephemeral binding to be used to initialize the
>> next one:
>>
>> auto _ = parse(input); // Parsed
>> auto _ = validate(_); // Validated
>> auto _ = normalize(_); // Normalized
>> consume(_);
>>
>> In:
>>
>> auto _ = validate(_);
>>
>> the _ inside the initializer denotes the previous binding.
>>
>> Only after initialization succeeds does the newly declared object become
>> the current _.
>>
>> I think this gives ordinary function calls a useful pipeline-like form
>> without introducing pipeline-specific syntax:
>>
>> auto _ = receive();
>> auto _ = decrypt(_);
>> auto _ = authenticate(_);
>> process(_);
>>
>> Conceptually, this means:
>>
>> receive
>> |
>> v
>> _
>> |
>> decrypt
>> |
>> v
>> _
>> |
>> authenticate
>> |
>> v
>> _
>>
>> Each intermediate object may have a completely different type.
>>
>> The important part is not that these objects are "unused". They clearly
>> are used.
>>
>> Rather, none of their individual identities are important enough to be
>> named and remembered.
>>
>> That leads to what I think is the central distinction:
>>
>> auto parsed = parse(input);
>>
>> says:
>>
>> this result has an identity worth naming and remembering.
>>
>> Whereas:
>>
>> auto _ = parse(input);
>>
>> would say:
>>
>> this result may be needed temporarily, but its identity is intentionally
>> non-persistent.
>>
>> A subsequent _ declaration therefore does not unexpectedly "shadow" a
>> meaningful identifier. Rebinding is part of the contract of _ itself.
>>
>> This is also why I think the normal objection to repeated declarations
>> does not apply in quite the same way.
>>
>> Silently changing what an ordinary name denotes would be dangerous:
>>
>> double x = compute();
>>
>> // ...
>>
>> int x = 42;
>>
>> // ...
>>
>> return x;
>>
>> The name x implies stable identity.
>>
>> But _ would imply precisely the opposite: code should not rely on that
>> binding having persistent identity.
>>
>> So I would summarize the idea as:
>>
>> _ should not mean "this variable is unused".
>>
>> _ should mean "this entity does not have an identity that the program is
>> expected to remember."
>>
>> "I don't care about this entity" is then one special case:
>>
>> auto [value, _] = f();
>>
>> "I need this entity briefly, but don't need to remember it afterwards" is
>> another:
>>
>> Socket _ = connect();
>> TlsConnection secure(_);
>>
>> And a sequence of ephemeral intermediate values naturally gives us:
>>
>> auto _ = parse(input);
>> auto _ = validate(_);
>> auto _ = normalize(_);
>> consume(_);
>>
>> I would initially keep this proposal strictly about name binding.
>>
>> In particular, I am *not* suggesting that using _ implicitly moves from
>> the previous object, changes its value category, shortens its lifetime, or
>> causes early destruction.
>>
>> For:
>>
>> auto _ = validate(_);
>>
>> the _ in the initializer would have the same value category it has under
>> ordinary C++ expression rules.
>>
>> Any storage reuse, copy elision, dead-store elimination, or similar
>> optimization should remain an implementation matter under the usual rules.
>>
>> The proposal would therefore be relatively narrow:
>>
>> 1.
>>
>> _ represents a non-persistent binding rather than merely an unused
>> variable.
>> 2.
>>
>> Multiple _ declarations may coexist in one scope.
>> 3.
>>
>> _ refers to the most recently established binding.
>> 4.
>>
>> A new binding becomes current only after its initializer has
>> completed.
>> 5.
>>
>> Rebinding _ has no effect on the lifetime of previously bound objects.
>>
>> I am interested in whether this is a useful generalization of the
>> existing name-independent declaration model, and in particular whether the
>> current ambiguity after multiple _ declarations is considered an
>> important safety property that should be preserved.
>>
>> Best,
>> Connor
>> --
>> Std-Proposals mailing list
>> Std-Proposals_at_[hidden]
>> https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals
>>
>
发件人: Connor Song <perdixky_at_[hidden]>
Date: 2026年8月13日周四 21:45
Subject: Re: [std-proposals] Generalizing `_` as a non-persistent binding
To: Simon Schröder <dr.simon.schroeder_at_[hidden]>
Hi Simon,
Yes, I am aware of P2169 and the C++26 rules for name-independent _
declarations.
So far, I have not found a case where this proposal would change the
meaning of an existing well-formed program.
My current view is that this is mostly an extension of the existing _
model: it gives well-defined semantics to some cases that are currently
ill-formed, rather than replacing existing behavior.
Of course, I may still be missing a compatibility corner case, and I would
be interested in any concrete examples.
Best,
Connor
Simon Schröder <dr.simon.schroeder_at_[hidden]> 于2026年8月13日周四 20:52写道:
> Are you aware of the existing rules for _ in C++26? Here is the original
> proposal:
> https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2023/p2169r4.pdf
>
> If you want to change/extend the behavior, it has to be compatible with
> the current standard (backwards compatibility).
>
> On Thu, Aug 13, 2026 at 2:32 PM Connor Song via Std-Proposals <
> std-proposals_at_[hidden]> wrote:
>
>> Hi all,
>>
>> I would like to propose a different way of thinking about _.
>>
>> I don't think _ should mean merely:
>>
>> this variable is unused.
>>
>> A more general interpretation might be:
>>
>> this entity does not have an identity that the program is expected to
>> remember.
>>
>> Or, less formally:
>>
>> I don't need to remember this entity, so I don't need to give it a
>> persistent identifier.
>>
>> I think this interpretation naturally covers two different cases which
>> are currently treated as somewhat separate concepts.
>>
>> The first case is the familiar one: the entity is not important and can
>> simply be ignored.
>>
>> For example:
>>
>> auto [result, _] = resolve();
>>
>> Here _ means that the second result exists, but its identity is
>> irrelevant to the rest of the program.
>>
>> But there is another case:
>>
>> The entity is important briefly, but its identity should not remain
>> available indefinitely.
>>
>> For example:
>>
>> Socket _ = connect();
>>
>> TlsConnection secure(_);
>>
>> Widget _ = make_widget();
>>
>> secure.send(data);
>>
>> Suppose TlsConnection borrows the Socket.
>>
>> The socket therefore cannot simply be destroyed after constructing secure;
>> it must remain alive according to the normal C++ lifetime rules.
>>
>> However, after constructing secure, there may be no reason for later
>> code to retain a stable name for the underlying socket.
>>
>> Writing:
>>
>> Socket raw = connect();
>> TlsConnection secure(raw);
>>
>> gives the underlying socket a persistent identity, and later code can
>> accidentally use it:
>>
>> raw.send(data); // accidentally bypasses TLS
>> secure.send(data);
>>
>> Using _ expresses something different:
>>
>> Socket _ = connect();
>> TlsConnection secure(_);
>>
>> The object is needed, and the binding is temporarily needed, but the
>> identity is not something later code is expected to remember.
>>
>> This suggests extending the existing name-independent _ model.
>>
>> Conceptually, I have the following rule in mind:
>>
>> Multiple name-independent declarations named _ may inhabit the same
>> scope. An id-expression _ denotes the most recently preceding such
>> declaration.
>>
>> The previous objects would not be destroyed when a new _ is declared.
>> Their lifetimes would remain completely unchanged.
>>
>> Only the binding denoted by _ changes.
>>
>> For example:
>>
>> Socket _ = connect(); // object A
>> TlsConnection secure(_); // refers to A
>>
>> Widget _ = make_widget(); // object B, `_` now denotes B
>>
>> Object A remains alive as usual. It merely no longer has the current _
>> binding.
>>
>> There is one additional rule I think is necessary:
>>
>> A new _ binding becomes the current _ only after its initializer has
>> been evaluated.
>>
>> This permits the previous ephemeral binding to be used to initialize the
>> next one:
>>
>> auto _ = parse(input); // Parsed
>> auto _ = validate(_); // Validated
>> auto _ = normalize(_); // Normalized
>> consume(_);
>>
>> In:
>>
>> auto _ = validate(_);
>>
>> the _ inside the initializer denotes the previous binding.
>>
>> Only after initialization succeeds does the newly declared object become
>> the current _.
>>
>> I think this gives ordinary function calls a useful pipeline-like form
>> without introducing pipeline-specific syntax:
>>
>> auto _ = receive();
>> auto _ = decrypt(_);
>> auto _ = authenticate(_);
>> process(_);
>>
>> Conceptually, this means:
>>
>> receive
>> |
>> v
>> _
>> |
>> decrypt
>> |
>> v
>> _
>> |
>> authenticate
>> |
>> v
>> _
>>
>> Each intermediate object may have a completely different type.
>>
>> The important part is not that these objects are "unused". They clearly
>> are used.
>>
>> Rather, none of their individual identities are important enough to be
>> named and remembered.
>>
>> That leads to what I think is the central distinction:
>>
>> auto parsed = parse(input);
>>
>> says:
>>
>> this result has an identity worth naming and remembering.
>>
>> Whereas:
>>
>> auto _ = parse(input);
>>
>> would say:
>>
>> this result may be needed temporarily, but its identity is intentionally
>> non-persistent.
>>
>> A subsequent _ declaration therefore does not unexpectedly "shadow" a
>> meaningful identifier. Rebinding is part of the contract of _ itself.
>>
>> This is also why I think the normal objection to repeated declarations
>> does not apply in quite the same way.
>>
>> Silently changing what an ordinary name denotes would be dangerous:
>>
>> double x = compute();
>>
>> // ...
>>
>> int x = 42;
>>
>> // ...
>>
>> return x;
>>
>> The name x implies stable identity.
>>
>> But _ would imply precisely the opposite: code should not rely on that
>> binding having persistent identity.
>>
>> So I would summarize the idea as:
>>
>> _ should not mean "this variable is unused".
>>
>> _ should mean "this entity does not have an identity that the program is
>> expected to remember."
>>
>> "I don't care about this entity" is then one special case:
>>
>> auto [value, _] = f();
>>
>> "I need this entity briefly, but don't need to remember it afterwards" is
>> another:
>>
>> Socket _ = connect();
>> TlsConnection secure(_);
>>
>> And a sequence of ephemeral intermediate values naturally gives us:
>>
>> auto _ = parse(input);
>> auto _ = validate(_);
>> auto _ = normalize(_);
>> consume(_);
>>
>> I would initially keep this proposal strictly about name binding.
>>
>> In particular, I am *not* suggesting that using _ implicitly moves from
>> the previous object, changes its value category, shortens its lifetime, or
>> causes early destruction.
>>
>> For:
>>
>> auto _ = validate(_);
>>
>> the _ in the initializer would have the same value category it has under
>> ordinary C++ expression rules.
>>
>> Any storage reuse, copy elision, dead-store elimination, or similar
>> optimization should remain an implementation matter under the usual rules.
>>
>> The proposal would therefore be relatively narrow:
>>
>> 1.
>>
>> _ represents a non-persistent binding rather than merely an unused
>> variable.
>> 2.
>>
>> Multiple _ declarations may coexist in one scope.
>> 3.
>>
>> _ refers to the most recently established binding.
>> 4.
>>
>> A new binding becomes current only after its initializer has
>> completed.
>> 5.
>>
>> Rebinding _ has no effect on the lifetime of previously bound objects.
>>
>> I am interested in whether this is a useful generalization of the
>> existing name-independent declaration model, and in particular whether the
>> current ambiguity after multiple _ declarations is considered an
>> important safety property that should be preserved.
>>
>> Best,
>> Connor
>> --
>> Std-Proposals mailing list
>> Std-Proposals_at_[hidden]
>> https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals
>>
>
Received on 2026-08-14 05:54:14
