9

I was surprised to see that this compiles in C++20 (GCC):

#include <iostream>

struct Test {
    int a;
    int b;
};

void main() {
    Test myTest(1, 2);
    std::cout << myTest.b; // 2
}

Changing struct to class or switching to C++17 throws the error I expected:

error: no matching function call to 'Test::Test(int, int)'

I tried googling, but could not find anything relevant. In C++20, is there a new kind of compiler-generated constructor for structs?

fordcars
  • 457
  • 3
  • 14
  • 1
    aggregate initialization (but with narrowing conversion allowed) is performed with `(..)` in C++20. – Jarod42 May 25 '23 at 16:14
  • See [direct_initialization](https://en.cppreference.com/w/cpp/language/direct_initialization) – Jarod42 May 25 '23 at 16:17
  • 1
    You can do on classes as well provided all members are `public`... – Aconcagua May 25 '23 at 16:21
  • @Jarod42 Actually: *can be performed* – previous variants (`SomeClass c{ }` and `SomeClass c = { ... }`) are still around as well... – Aconcagua May 25 '23 at 16:28
  • @Aconcagua: `{..}` disallows narrowing conversion contrary to `(..)`. – Jarod42 May 25 '23 at 16:31
  • @Jarod42 Still doesn't mean that `()` is the only way, and `= { }` *does* allow as well... – Aconcagua May 25 '23 at 16:38
  • FWIW, this fixes the problem of using aggregates in a lot of standard library components that do `T(std::forward(args)...);`. That syntax does not allows `T` to be an aggregate type and caused a number of headaches. – NathanOliver May 25 '23 at 16:38

1 Answers1

11

This is p0960r3, which allows aggregate initialization from a parenthized list of values. struct or class is (as typically) a red herring here: your original struct Test class is an aggregate, but when you change its members to fall under private access rules it is no longer an aggregate. The rules for aggregates and aggregate initialization are notoriously ever-changing as the language evolves, see e.g. The Fickle Aggregate for details.

I tried googling, but could not find anything relevant.

If you find a new feature of a given language version, and you do not understand how or why it was introduced, the open-std delta pages can be a good place to start:

Changes between C++17 and C++20 DIS

[...]

(P0960R3, P1975R0) Aggregate initialization from a parenthesized list

Aggregates can now be initialized with round parentheses instead of curly braces: struct T { int a, b, c; }; T x(1, 2); A (motivating) consequence is that make_unique and emplace now work with aggregate types.

dfrib
  • 70,367
  • 12
  • 127
  • 192
  • Worth noting: `SomeStruct s = { ... };`, origin of aggregate initialisation, has even been possible since (at least) C++98... – Aconcagua May 25 '23 at 16:24
  • @Aconcagua I added a link to an somewhat thorough article on the topic instead, as there are many oddities worth mentioning if one wants to dig into the language deltas when it comes to aggregate initialization rules. – dfrib May 25 '23 at 16:27
  • Just noticing: `A a1{1, f()}; // OK, lifetime is extended A a2(1, f()); // well-formed, but dangling reference` – what the [...] Have they gone entirely mad? Why that difference? Did it *really* make it into the standard (appears indeed so)? – Aconcagua May 25 '23 at 16:36
  • @Aconcagua IIRC the rationale was that since it looks like a constructor call (rather than aggregate initialization), it should behave like a constructor call (narrowing allowed, no lifetime extension), however the underlying language mechanism that allows it is not generation of synthesized member-wise constructors (which was an earlier option) but that of aggregate initialization. The section _Kona 2019 CWG review feedback_ of the paper describes the move from the initial strategy of invented constructors to instead use that of aggregate initialization. – dfrib May 25 '23 at 16:54
  • Interesting link (though not really concerning my initial comment...). Actually missing there: User-*provided* constructor is a safe means over all versions, too (would leave a comment, but not going to have yet another account somewhere...). – Aconcagua May 25 '23 at 17:00
  • @Aconcagua (on-topic) Imho struct-like classes with reference members and no constructors (as opposed to classes with reference members and appropriate constructors) is somewhat of a code smell, and the existing temporary lifetime extension rules have unfortunately introduced an expectation of a feature based on nitty-gritty language rules to be a trust band-aid for code smell design. I don't see this dangling reference example as a problem, as any sounds code quality linter (e.g. in my own domain of safety-critical) would reject such code. – dfrib May 25 '23 at 17:01
  • (/topic-switching again) Note that I'm the author of that article: the article is particularly intended to visit the language rule changes concerning aggregates, and I'm avoiding any kind of recommendations of coding patterns relating directly or tangential to the topic, so whilst user-provided constructor may be a good advice in many context, such an advice, imho, does not fit into the scope of my article. – dfrib May 25 '23 at 17:04
  • Ah, well, there I need to fully agree (smell on references without constructor). It isn't the dangling reference I see in the first place, but the difference between the two variants. My personal conclusion from thus is: Lifetime extension here has been a plain fault, making the language yet another time more complicated without need (but I consider the whole uniform initialisation feature flawed anyway...). About the advice: Well, you gave one variant anyway (making a variable private/protected) – cannot see any difference in respect to fitting or not... Either way, the article is great! – Aconcagua May 25 '23 at 17:13
  • Just for completeness: Lifetime extension hasn't been introduced with `= {}` syntax, or have I missed something once more? – Aconcagua May 25 '23 at 17:17
  • Thanks for the `struct` and `class` aggregate explanation @dfrib, that's what I was missing! – fordcars May 25 '23 at 17:21
  • @Aconcagua `= {}` is copy-list initialization and not related to lifetime extension (in the sense we are discussing: binding to const lvalue references and rvalue references). – dfrib May 25 '23 at 17:30
  • In addition to `make_unique` and `emplace`, this new feature enables `std::in_place_type` for variants. – Seth Johnson Sep 01 '23 at 02:18