---------- Forwarded message ---------
发件人: Connor Song <perdixky@gmail.com>
Date: 2026年8月13日周四 21:45
Subject: Re: [std-proposals] Generalizing `_` as a non-persistent binding
To: Simon Schröder <dr.simon.schroeder@gmail.com>


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@gmail.com> 于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@lists.isocpp.org> 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@lists.isocpp.org
https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals