Has the C++17 extension to aggregate initialization made brace initialization dangerous?

struct D and struct E represent two completely unrelated types.

But they’re not “completely unrelated” types. They both have the same base class type. This means that every D is implicitly convertible to a B. And therefore every D is a B. So doing E e{d}; is no different from E e{b}; in terms of the invoked operation.

You cannot turn off implicit conversion to base classes.

If this truly bothers you, the only solution is to prevent aggregate initialization by providing an appropriate constructor(s) that forwards the values to the members.

As for whether this makes aggregate initialization more dangerous, I don’t think so. You could reproduce the above circumstances with these structs:

struct B { int i; };
struct D { B b; char j; operator B() {return b;} };
struct E { B b; float k; };

So something of this nature was always a possibility. I don’t think that using implicit base class conversion makes it that much “worse”.

A deeper question is why a user tried to initialize an E with a D to begin with.

the idea that you can silently convert from a circle to a rectangle, that to me is a problem.

You would have the same problem if you did this:

struct rectangle
{
  rectangle(point p);

  int sx; int sy;
  point p;
};

You can not only perform rectangle r{c}; but rectangle r(c).

Your problem is that you’re using inheritance incorrectly. You’re saying things about the relationship between circle, rectangle and point which you don’t mean. And therefore, the compiler lets you do things you didn’t mean to do.

If you had used containment instead of inheritance, this wouldn’t be a problem:

struct point { int x; int y; };
struct circle { point center; int r; };
struct rectangle { point top_left; int sx; int sy; };

void move( point& p );

void f( circle c )
{
  move( c ); // Error, as it should, since a circle is not a point.
  rectangle r1( c );  // Error, as it should be
  rectangle r2{ c };  // Error, as it should be.
}

Either circle is always a point, or it is never a point. You’re trying to make it a point sometimes and not others. That’s logically incoherent. If you create logically incoherent types, then you can write logically incoherent code.


the idea that you can silently convert from a circle to a rectangle, that to me is a problem.

This brings up an important point. Conversion, strictly speaking, looks like this:

circle cr = ...
rectangle rect = cr;

That is ill-formed. When you do rectangle rect = {cr};, you’re not doing a conversion. You are explicitly invoking list-initialization, which for an aggregate will usually provoke aggregate initialization.

Now, list-initialization certainly can perform a conversion. But given merely D d = {e};, one should not expect that this means you’re performing conversion from an e to a D. You’re list-initializing an object of type D with an e. That can perform conversion if E is convertible to D, but this initialization can still be valid if non-conversion list-initialization forms can work too.

So it is incorrect to say that this feature makes circle convertible to rectangle.

Leave a Comment

Hata!: SQLSTATE[HY000] [1045] Access denied for user 'divattrend_liink'@'localhost' (using password: YES)