When is P1008 ("prohibit aggregates with user-declared constructors") useful in practice?
Asked Answered
S

1

6

P1008 ("Prohibit aggregates with user-declared constructors") has become part of the C++20 standard, in order to prevent surprising behavior when using aggregate initialization:

struct X {
  int i{42};
  X() = delete;
};

int main() {
  X x2{3}; // Compiles in C++17, error in C++20
}

I agree that the above X x2{3}; statement should not compile. However, all the examples justifying P1008 that I've encountered are not realistic at all - they are purely syntactical and basically meaningless foo/bar/baz code snippets.

What problem does P1008 solve in practice? I find it hard to imagine how I would end up writing something like the above X in a real program.

Deleting the default constructor in a C++17 aggregate without providing other constructors to initialize it seems unrealistic to me.

Straley answered 13/11, 2019 at 15:48 Comment(4)
I feel like the ship has sailed for this particular question. For what it's worth, I was mildly surprised when I ran into this case the first time, leading to this question (although now I don't remember the original example), but like... yeah.Deeannadeeanne
@Barry: I don't want to write a counter-proposal. I'm just curious about the real-world cases affected by P1008... if anyStraley
@Barry: even your example is not convincing. Why would you have a class with a =default private constructor and all public data members?Straley
@VittorioRomeo: Because anyone who is able to get the type should be able to access the variables. It's creating new values for the type that is prohibited outside of private access classes. If you're given access to an existing value, you can still work on it as normal.Slopwork
S
5

The most obvious case is this:

struct X
{
private:
    X() = default;
};

X x{};

This is not a type which should be able to be initialized outside of a privately accessible context. But it can be.

Now, such types might seem silly, but they're actually useful for implementing private functions that work through forwarding functions. make_shared for example cannot call constructors declared private, even if you make the make_shared template a friend. So instead, you make the constructors public, but require that the user pass an instance of a type that can only be constructed by someone with private access. So X would either be a member type of the target class or X would make the target class a friend.

Slopwork answered 13/11, 2019 at 16:0 Comment(5)
Assuming I understand correctly, you are referring to the "passkey idiom". Honestly, it doesn't seem very motivating for the impact P1008 had, but at least it is somewhat realistic.Straley
@VittorioRomeo: What impact did it have, exactly? It seems to me that any code it breaks is code that isn't following the Rule of 0 and thus probably deserves to be broken.Slopwork
It is arguable whether "the rule of zero" is exactly "not specifying any special member function" or also includes "specifying all special member functions as =default". In my mental model these were equivalent. This is an example of impact: twitter.com/tcanens/status/1185379657350352897Straley
@VittorioRomeo: array's destructor should not be unconditionally noexcept. To me, this seems to be an example of why this is a good thing. If a type cares enough about what the subobjects are doing that it wants to enforce noexcept destruction, then I would say that it's not a logical aggregation. An aggregate object ought to be functionally identical to declaring the contents of the aggregate as variables in the same scope. That includes noexcept destructors of those types. This is the feature working as intended to me.Slopwork
This type is trivial, so if you can name it you can bless an object into existence, private constructor or not. If you make the name private, then the ctor's privateness isn't very relevant.Earleanearleen

© 2022 - 2024 — McMap. All rights reserved.