What are the rules for _ underscore variables in C++26, and what's a name-independent declaration?
Asked Answered
M

1

12

When I compile the following code, I get a warning (https://godbolt.org/z/Tx7v6jWf1):

void foo() {
    int _;
    // warning: name-independent declarations only available with
    //           '-std=c++2c' or '-std=gnu++2c' [-Wc++26-extensions]
    int _;
}

What exactly has changed about _ variables in C++26, and what's a name-indepent declaration?

Meggy answered 7/1 at 12:38 Comment(0)
M
17

P2169: A nice placeholder with no name has been accepted into C++26, which makes _ special in the following contexts:

  • local variables (e.g. int _)
  • local structured bindings (e.g. auto [x, _])
  • init-captures (e.g. [_ = 0] {})
  • non-static data members of other than an anonymous union (e.g. struct S { int _; })

In such contexts, _ makes the declaration name-independent.

_ suppresses warnings

The standard says:

Recommended practice: Implementations should not emit a warning that a name-independent declaration is used or unused.

This is very similar to the recommendation for [[maybe_unused]] in [dcl.attr.unused] p4. Normally, you would get a warning for unused variables (-Wunused in GCC), but _ and [[maybe_unused]] suppress this.

Historically, developers have used _ as a "placeholder" for unused things, so this is just standardizing existing practice.

_ can be declared multiple times

Furthermore, name-independent declarations cannot potentially conflict. In short, you can declare _ multiple times. However, name lookup cannot be ambiguous.

void g() {
  int _;
  _ = 0;   // OK, and warning is not recommended
  int _;   // OK, name-independent declaration does not potentially conflict with the first _
  _ = 0;   // error: two non-function declarations in the lookup set
}

This code is taken from [basic.scope.scope] example 3.

Note that _ also has some special interactions with using declarations. See [namespace.udecl] p10 for more details.

Two _ are not the same entity from the linker's perspective

Even with external linkage, two _ are not considered the same entity:

// a.cpp
int _ = 0;
// b.cpp
int _ = 0;

When linked, this program is OK. For any name other than _, this would give you a "multiple definitions" linker error. See also [basic.link] p8.

Compiler Support

Only GCC 14 and Clang 18 support this feature at the time of writing. See C++26 compiler support for more.

If you need to test for support, test for __cpp_placeholder_variables:

#if __cpp_placeholder_variables >= 202306L
Meggy answered 7/1 at 12:38 Comment(6)
And it is again the committee that had ignored the wish of the larger community and went with cryptic syntax and unintuitive behaviour. using std::ignore would have been so much better and be more consistent. But now they want to add this mess. static changes how _ is interpreted completely, you are allowed to read and write to that variable, you are allowed to have multiple otherwise conflicting declarations but only if not reading/asigning values!?! That is the opposite of what the proposal was supposed to solve. This will be the source of so many problems again.Interstate
@Interstate P26968: Make std::ignore a first-class object is likely going to be accepted into C++26 as well.Meggy
@Interstate In no way is this "cryptic syntax and unintuitive behavior" and in no way would using std::ignore to solve this problem be in any way "much better" or "more consistent." ignore = lock_guard{mtx} would immediately drop the lock, which is exactly not what you want.Gormley
@Gormley In what way is it NOT cryptic when variables with a specific NAME compile, link and behave completely different from all other variables? And your example you explicitly do NOT want to ignore the variable so naming it _ before was fine so again the change will not help in any way shape or formInterstate
On the other hand this proposal would allow things like declaring multiple variables with the same identifier and getting no warnings what so ever till the program is ill-defined just cause you wanted to make sure that the value you ignored really is unimportant. With that proposal std::lock_guard _{mtx}; ... <insert some code> auto [x,] = f();` would compile but std::lock_guard _{mtx}; ... <insert some code> auto [x,] = f(); .... asser( _ == nullptr);` would give you cryptic errors. Now tell me - what exactly do you propose becomes better?Interstate
@Interstate The error is not at all cryptic - it is very clear why it is ambiguous and the error here points you to all the variables.Gormley

© 2022 - 2024 — McMap. All rights reserved.