Logo

Blog


Aggregates: C++17 vs. C++20

Sometimes the small changes between two C++ standards really bite you. Today's post is about when I got bitten by a change to aggregates in C++20.

A harmless example

Attendees of my training classes usually assume that I know everything. I can say sorry, but that's not the case. One day in the past, I showed the following example during a class:

1
2
3
4
5
6
struct Point {
  int x;
  int y;
};

Point pt{2, 3};

The class did cover C++17 and C++20. The code of Point is a reduced version for this post. We were talking about C++17's structured bindings. I use Point to show the decomposition using C++ Insights.

I showed the behavior and certain variations while answering questions from the attendees. One question was about move and copy. The question leads me changing the initial code to the following one:

1
2
3
4
5
6
7
8
struct Point {
  int x;
  int y;

  Point(Point&&) = delete;  A 
};

Point pt{2, 3};

As you can see, in A, I deleted the move constructor. As far as I remember, this topic was more or less the last one for this day. Everything went well and as expected.

Works on my machine

The next morning one of the attendees approached me with the question of why the code didn't compile on his machine. My usual answer here is that if C++ were be easy, I wouldn't have the job I have. Somehow this never shuts down such questions. I like curious attendees.

He shared his code via Compiler Explorer. Guess what? His code looked exactly like mine but didn't compile. Here is the error message from the compiler:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
<source>:12:9: error: no matching constructor for initialization of 'Point'
  Point pt{2, 3};
        ^ ~~~~~~
<source>:9:5: note: candidate constructor not viable: requires 1 argument, but 2 were provided
    Point(Point&&) = delete;
    ^
<source>:5:10: note: candidate constructor (the implicit copy constructor) not viable: requires 1 argument, but 2 were provided
  struct Point {
         ^
1 error generated.
Compiler returned: 1

Rules for aggregates

Do you remember that I told you the class did also cover C++20? Since structured bindings have been introduced with C++17, my example did use C++17. Since the bug reporter also played with C++20 features, his environment was set to C++20.

What should I say? The code no longer compiles in C++20. The reason is that in C++20, an aggregate cannot have a user-declared constructor, among other restrictions. This was the case before C++11 but was changed with C++11 and once more with C++20.

Due to that change, Point is no longer an aggregate; hence, the compiler does not accept the initialization of Point as no constructor is present.

Take away

Be aware of the small changes between the standards. While I think the C++20 behavior is correct, I still wrote that piece of code. It is unlikely that I would write such code in production, but you never know.

If you want to know more about aggregates and PODs, check out my post C++20: Aggregate, POD, trivial type, standard layout class, what is what.

Andreas