Discussion:
Less strict aggregate initialization
(too old to reply)
m***@gmail.com
2018-11-09 22:42:37 UTC
Permalink
Hello, consider this code in context of latest draft:

struct foo
{
int val;
protected:
foo(int v = 42) : val{ v } {}
};


struct bar : foo
{};


int main()
{
bar b{};
return b.val;
}


Here we have:
-foo class with protected ctor
-bar class that is an aggregate

In the main we create object b of type bar. There are empty braces, so it's
an aggregate initialization. Because of its rules, bar's base class - foo,
should be value initialized, but..
In [dcl.init.aggr]/1 note (http://eel.is/c++draft/dcl.init.aggr#1) we can
Aggregate initialization does not allow accessing protected and private
base class' members or constructors.
Because of that above code will not compile. Such code doesn't compile as
well:


struct qux;

class baz
{
baz() : m_val{ 42 } {}


public:
int m_val;
friend qux;
};

struct qux
{
baz b;
};

int main()
{
qux q{};
return q.b.m_val;
}



I was wondering if it is desired. If we force value initialization instead,
everything works like a charm:


struct foo
{
int val;
protected:
foo(int v = 42) : val{ v } {}
};


struct bar : foo
{};


struct qux;


class baz
{
baz() : val{ 42 } {}


public:
int val;
friend qux;
};


struct qux
{
baz b;
};


int main()
{
return bar().val + qux().b.val;
}



Shouldn't such aggregate initializations be permitted? Reasons to change
that I can think of, on top of my head:
-consistency with other initializations
-it's more intuitive. With type{} expression I'd like to construct object
in a way like its default ctor would be called, from which protected and
befriended stuff is accessible (well, that's basically same as above).
Aggregate initialization does not allow accessing protected and private
base class' members or constructors.
to
Aggregate initialization does not allow accessing protected and private
base class' members or constructors directly in initializer-list.
Otherwise, if the element is not a reference, the element is
copy-initialized from an empty initializer list ([dcl.init.list]).
to
Otherwise, if the element is not a reference, the element is
copy-initialized from an empty initializer list ([dcl.init.list]).
Element's accessibility is the same as it would be accessed in aggregate
object constructor's initializer list.
My questions:
-Did I understand everything right about current wording?
-Is my proposition even a thing?
-If it is, should it be formed as issue/paper/something else?

Thanks,
Mateusz Janek
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Discussion" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-discussion+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at https://groups.google.com/a/isocpp.org/group/std-discussion/.
Balog Pal
2018-11-10 18:05:31 UTC
Permalink
2018. november 9., péntek 23:42:37 UTC+1 időpontban ***@gmail.com a
következőt írta:

I don't see a problem with your examples. You play with access and get
exactly where it follows. In the aggregate-init form, you're in the context
of the function. That function is not friend, so it rejects the attempts to
get to the ctors. If you befriend the function, then it starts working.

In the last example you invoke the (default-generated) ctor of the class.
That can works fine as you did grant friendship to that descendant, and the
function itself does not attempt to work around the protection.
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Discussion" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-discussion+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at https://groups.google.com/a/isocpp.org/group/std-discussion/.
m***@gmail.com
2018-11-12 19:01:30 UTC
Permalink
Thank you for the reply!
You play with access and get exactly where it follows.
Actually, that is exactly my point. To discuss whether such code should be
treated as playing with the access.

As you can see, user of those classes didn't explicitly use stuff that he
doesn't have access to. It was done implicitly by the compiler, which
follows the standard rules.

Consider this:
struct foo
{
int val;
protected:
foo(int v = 42) : val{ v } {}
};


struct bar : foo
{};


struct baz : foo
{
baz() = default;
};


int main()
{
// Doesn't compile
//bar b{};
//return b.val;


baz b{};
return b.val;
}


In both usages of derived classes, used didn't explicitly call foo's ctor.
First usage `bar b{}` doesn't compile because the `bar` is an aggregate.
Second one compiles because `baz` is not an aggregate.

From user perspective these two classes are completely the same. Why would
we complicate user's life with such restrictions ([dcl.init.aggr]/1 note).

I like to think about the aggregate initialization as a feature that helps
me reduce amount of code that needs to be written to initialize stuff. IMHO
it shouldn't tell user that he uses stuff without access, when he actually
doesn't do that.


To be clear.
I'm not talking about explicit usages of forbidden stuff, e.g.
struct foo
{
int val;
protected:
foo(int v = 42) : val{ v } {}
};


struct bar : foo
{};


int main()
{
bar b{{0}};
return b.val;
}
It should not compile for sure.

I'm talking about ctors and members omitted in initializer list, to which
aggregate has access.


P.S.
off the top of my head*
I needed to correct that.

W dniu sobota, 10 listopada 2018 19:05:32 UTC+1 uÅŒytkownik Balog Pal
I don't see a problem with your examples. You play with access and get
exactly where it follows. In the aggregate-init form, you're in the context
of the function. That function is not friend, so it rejects the attempts to
get to the ctors. If you befriend the function, then it starts working.
In the last example you invoke the (default-generated) ctor of the class.
That can works fine as you did grant friendship to that descendant, and the
function itself does not attempt to work around the protection.
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Discussion" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-discussion+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at https://groups.google.com/a/isocpp.org/group/std-discussion/.
Richard Smith
2018-11-12 19:34:13 UTC
Permalink
Post by m***@gmail.com
Thank you for the reply!
You play with access and get exactly where it follows.
Actually, that is exactly my point. To discuss whether such code should be
treated as playing with the access.
As you can see, user of those classes didn't explicitly use stuff that he
doesn't have access to. It was done implicitly by the compiler, which
follows the standard rules.
struct foo
{
int val;
foo(int v = 42) : val{ v } {}
};
struct bar : foo
{};
struct baz : foo
{
baz() = default;
};
int main()
{
// Doesn't compile
//bar b{};
//return b.val;
baz b{};
return b.val;
}
In both usages of derived classes, used didn't explicitly call foo's ctor.
First usage `bar b{}` doesn't compile because the `bar` is an aggregate.
Second one compiles because `baz` is not an aggregate.
From user perspective these two classes are completely the same. Why would
we complicate user's life with such restrictions ([dcl.init.aggr]/1 note).
Perhaps this example will help:

bar b0{ foo{0} }; // clearly ill-formed, the foo{0} is invoking a protected
constructor from a context with no access
bar b1{ {0} }; // same
bar b2{ 0 }; // same
bar b3{ {} }; // same
bar b4{}; // by definition, for an aggregate, this is shorthand for b3

The heart of the problem here is that you're using the "bar b{}"
initialization syntax, which has no associated semantics other than "do
what I mean". In this case it's not clear what you mean, and the language
rules say that aggregate initialization wins over calling constructors.
Post by m***@gmail.com
I like to think about the aggregate initialization as a feature that helps
me reduce amount of code that needs to be written to initialize stuff. IMHO
it shouldn't tell user that he uses stuff without access, when he actually
doesn't do that.
To be clear.
I'm not talking about explicit usages of forbidden stuff, e.g.
struct foo
{
int val;
foo(int v = 42) : val{ v } {}
};
struct bar : foo
{};
int main()
{
bar b{{0}};
return b.val;
}
It should not compile for sure.
I'm talking about ctors and members omitted in initializer list, to which
aggregate has access.
P.S.
off the top of my head*
I needed to correct that.
W dniu sobota, 10 listopada 2018 19:05:32 UTC+1 uÅŒytkownik Balog Pal
I don't see a problem with your examples. You play with access and get
exactly where it follows. In the aggregate-init form, you're in the context
of the function. That function is not friend, so it rejects the attempts to
get to the ctors. If you befriend the function, then it starts working.
In the last example you invoke the (default-generated) ctor of the class.
That can works fine as you did grant friendship to that descendant, and the
function itself does not attempt to work around the protection.
--
---
You received this message because you are subscribed to the Google Groups
"ISO C++ Standard - Discussion" group.
To unsubscribe from this group and stop receiving emails from it, send an
Visit this group at
https://groups.google.com/a/isocpp.org/group/std-discussion/.
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Discussion" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-discussion+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at https://groups.google.com/a/isocpp.org/group/std-discussion/.
m***@gmail.com
2018-11-12 23:17:54 UTC
Permalink
Thanks for the reply.

If I understand this correctly, such syntax moves whole responsibility to
the user-side. User needs to know which kind of initialization will be
performed, whether object is an aggregate, if it is, care about accesses
etc. Am I right?
In this case it's not clear what you mean, and the language rules say that
aggregate initialization wins over calling constructors.

Yep, it's clear. It is how it works now and I fully understand that.

Maybe it's a silly question (unfortunately I'm not familiar with standard
and decisions that were made, as I would like to be) but was such syntax
"bar b{}" designed to work this way? Could you please explain me, why such
aggregate initialization could not initialize `foo` with accesses that
`bar` has?

In other words, what could possibly go wrong with my small proposal
(probably with different wording, but you get the idea)?


W dniu poniedziałek, 12 listopada 2018 20:34:30 UTC+1 uÅŒytkownik Richard
Post by m***@gmail.com
Thank you for the reply!
You play with access and get exactly where it follows.
Actually, that is exactly my point. To discuss whether such code should
be treated as playing with the access.
As you can see, user of those classes didn't explicitly use stuff that he
doesn't have access to. It was done implicitly by the compiler, which
follows the standard rules.
struct foo
{
int val;
foo(int v = 42) : val{ v } {}
};
struct bar : foo
{};
struct baz : foo
{
baz() = default;
};
int main()
{
// Doesn't compile
//bar b{};
//return b.val;
baz b{};
return b.val;
}
In both usages of derived classes, used didn't explicitly call foo's ctor.
First usage `bar b{}` doesn't compile because the `bar` is an aggregate.
Second one compiles because `baz` is not an aggregate.
From user perspective these two classes are completely the same. Why
would we complicate user's life with such restrictions ([dcl.init.aggr]/1
note).
bar b0{ foo{0} }; // clearly ill-formed, the foo{0} is invoking a
protected constructor from a context with no access
bar b1{ {0} }; // same
bar b2{ 0 }; // same
bar b3{ {} }; // same
bar b4{}; // by definition, for an aggregate, this is shorthand for b3
The heart of the problem here is that you're using the "bar b{}"
initialization syntax, which has no associated semantics other than "do
what I mean". In this case it's not clear what you mean, and the language
rules say that aggregate initialization wins over calling constructors.
Post by m***@gmail.com
I like to think about the aggregate initialization as a feature that
helps me reduce amount of code that needs to be written to initialize
stuff. IMHO it shouldn't tell user that he uses stuff without access, when
he actually doesn't do that.
To be clear.
I'm not talking about explicit usages of forbidden stuff, e.g.
struct foo
{
int val;
foo(int v = 42) : val{ v } {}
};
struct bar : foo
{};
int main()
{
bar b{{0}};
return b.val;
}
It should not compile for sure.
I'm talking about ctors and members omitted in initializer list, to which
aggregate has access.
P.S.
off the top of my head*
I needed to correct that.
W dniu sobota, 10 listopada 2018 19:05:32 UTC+1 uÅŒytkownik Balog Pal
I don't see a problem with your examples. You play with access and get
exactly where it follows. In the aggregate-init form, you're in the context
of the function. That function is not friend, so it rejects the attempts to
get to the ctors. If you befriend the function, then it starts working.
In the last example you invoke the (default-generated) ctor of the
class. That can works fine as you did grant friendship to that descendant,
and the function itself does not attempt to work around the protection.
--
---
You received this message because you are subscribed to the Google Groups
"ISO C++ Standard - Discussion" group.
To unsubscribe from this group and stop receiving emails from it, send an
Visit this group at
https://groups.google.com/a/isocpp.org/group/std-discussion/.
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Discussion" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-discussion+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at https://groups.google.com/a/isocpp.org/group/std-discussion/.
Richard Smith
2018-11-13 00:31:31 UTC
Permalink
Post by m***@gmail.com
Thanks for the reply.
If I understand this correctly, such syntax moves whole responsibility to
the user-side. User needs to know which kind of initialization will be
performed, whether object is an aggregate, if it is, care about accesses
etc. Am I right?
Yes, that's roughly how I view it. Because uniform initialization syntax
can do anything, there is more scope for it to do something other than what
you meant, and a change to the type you're initializing (or, as in this
case, to the language) can change your initialization from one meaning to
another.
Post by m***@gmail.com
In this case it's not clear what you mean, and the language rules say that
aggregate initialization wins over calling constructors.
Yep, it's clear. It is how it works now and I fully understand that.
Maybe it's a silly question (unfortunately I'm not familiar with standard
and decisions that were made, as I would like to be) but was such syntax
"bar b{}" designed to work this way? Could you please explain me, why such
aggregate initialization could not initialize `foo` with accesses that
`bar` has?
In other words, what could possibly go wrong with my small proposal
(probably with different wording, but you get the idea)?
It could work the way you describe. For a base class, we could imagine that
the implicit initializer is in some sense "inside" the derived class
instead of at the point of aggregate initialization, so that the access
check would succeed. This would make the initialization rules less uniform
-- an omitted initializer would no longer be equivalent to a {} initializer
-- but I don't know of any other disadvantage to it. (The non-uniformity is
similar to what happens for default member initializers, though -- they are
access checked in the context of the derived class (where they appear) and
aren't equivalent to {}.)
Post by m***@gmail.com
W dniu poniedziałek, 12 listopada 2018 20:34:30 UTC+1 uÅŒytkownik Richard
Post by Richard Smith
Post by m***@gmail.com
Thank you for the reply!
You play with access and get exactly where it follows.
Actually, that is exactly my point. To discuss whether such code should
be treated as playing with the access.
As you can see, user of those classes didn't explicitly use stuff that
he doesn't have access to. It was done implicitly by the compiler, which
follows the standard rules.
struct foo
{
int val;
foo(int v = 42) : val{ v } {}
};
struct bar : foo
{};
struct baz : foo
{
baz() = default;
};
int main()
{
// Doesn't compile
//bar b{};
//return b.val;
baz b{};
return b.val;
}
In both usages of derived classes, used didn't explicitly call foo's ctor.
First usage `bar b{}` doesn't compile because the `bar` is an aggregate.
Second one compiles because `baz` is not an aggregate.
From user perspective these two classes are completely the same. Why
would we complicate user's life with such restrictions ([dcl.init.aggr]/1
note).
bar b0{ foo{0} }; // clearly ill-formed, the foo{0} is invoking a
protected constructor from a context with no access
bar b1{ {0} }; // same
bar b2{ 0 }; // same
bar b3{ {} }; // same
bar b4{}; // by definition, for an aggregate, this is shorthand for b3
The heart of the problem here is that you're using the "bar b{}"
initialization syntax, which has no associated semantics other than "do
what I mean". In this case it's not clear what you mean, and the language
rules say that aggregate initialization wins over calling constructors.
Post by m***@gmail.com
I like to think about the aggregate initialization as a feature that
helps me reduce amount of code that needs to be written to initialize
stuff. IMHO it shouldn't tell user that he uses stuff without access, when
he actually doesn't do that.
To be clear.
I'm not talking about explicit usages of forbidden stuff, e.g.
struct foo
{
int val;
foo(int v = 42) : val{ v } {}
};
struct bar : foo
{};
int main()
{
bar b{{0}};
return b.val;
}
It should not compile for sure.
I'm talking about ctors and members omitted in initializer list, to
which aggregate has access.
P.S.
off the top of my head*
I needed to correct that.
W dniu sobota, 10 listopada 2018 19:05:32 UTC+1 uÅŒytkownik Balog Pal
2018. november 9., péntek 23:42:37 UTC+1 időpontban
I don't see a problem with your examples. You play with access and get
exactly where it follows. In the aggregate-init form, you're in the context
of the function. That function is not friend, so it rejects the attempts to
get to the ctors. If you befriend the function, then it starts working.
In the last example you invoke the (default-generated) ctor of the
class. That can works fine as you did grant friendship to that descendant,
and the function itself does not attempt to work around the protection.
--
---
You received this message because you are subscribed to the Google
Groups "ISO C++ Standard - Discussion" group.
To unsubscribe from this group and stop receiving emails from it, send
Visit this group at
https://groups.google.com/a/isocpp.org/group/std-discussion/.
--
---
You received this message because you are subscribed to the Google Groups
"ISO C++ Standard - Discussion" group.
To unsubscribe from this group and stop receiving emails from it, send an
Visit this group at
https://groups.google.com/a/isocpp.org/group/std-discussion/.
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Discussion" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-discussion+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at https://groups.google.com/a/isocpp.org/group/std-discussion/.
m***@gmail.com
2018-11-13 22:33:19 UTC
Permalink
Do you think that it's a good idea to prepare a tiny formal paper and post
it to the 'ISO C++ Standard - Future Proposals' group for discussion?

W dniu wtorek, 13 listopada 2018 01:31:50 UTC+1 uÅŒytkownik Richard Smith
Post by Richard Smith
Post by m***@gmail.com
Thanks for the reply.
If I understand this correctly, such syntax moves whole responsibility to
the user-side. User needs to know which kind of initialization will be
performed, whether object is an aggregate, if it is, care about accesses
etc. Am I right?
Yes, that's roughly how I view it. Because uniform initialization syntax
can do anything, there is more scope for it to do something other than what
you meant, and a change to the type you're initializing (or, as in this
case, to the language) can change your initialization from one meaning to
another.
Post by m***@gmail.com
In this case it's not clear what you mean, and the language rules say
that aggregate initialization wins over calling constructors.
Yep, it's clear. It is how it works now and I fully understand that.
Maybe it's a silly question (unfortunately I'm not familiar with standard
and decisions that were made, as I would like to be) but was such syntax
"bar b{}" designed to work this way? Could you please explain me, why such
aggregate initialization could not initialize `foo` with accesses that
`bar` has?
In other words, what could possibly go wrong with my small proposal
(probably with different wording, but you get the idea)?
It could work the way you describe. For a base class, we could imagine
that the implicit initializer is in some sense "inside" the derived class
instead of at the point of aggregate initialization, so that the access
check would succeed. This would make the initialization rules less uniform
-- an omitted initializer would no longer be equivalent to a {} initializer
-- but I don't know of any other disadvantage to it. (The non-uniformity is
similar to what happens for default member initializers, though -- they are
access checked in the context of the derived class (where they appear) and
aren't equivalent to {}.)
Post by m***@gmail.com
W dniu poniedziałek, 12 listopada 2018 20:34:30 UTC+1 uÅŒytkownik Richard
Post by Richard Smith
Post by m***@gmail.com
Thank you for the reply!
You play with access and get exactly where it follows.
Actually, that is exactly my point. To discuss whether such code should
be treated as playing with the access.
As you can see, user of those classes didn't explicitly use stuff that
he doesn't have access to. It was done implicitly by the compiler, which
follows the standard rules.
struct foo
{
int val;
foo(int v = 42) : val{ v } {}
};
struct bar : foo
{};
struct baz : foo
{
baz() = default;
};
int main()
{
// Doesn't compile
//bar b{};
//return b.val;
baz b{};
return b.val;
}
In both usages of derived classes, used didn't explicitly call foo's ctor.
First usage `bar b{}` doesn't compile because the `bar` is an
aggregate. Second one compiles because `baz` is not an aggregate.
From user perspective these two classes are completely the same. Why
would we complicate user's life with such restrictions ([dcl.init.aggr]/1
note).
bar b0{ foo{0} }; // clearly ill-formed, the foo{0} is invoking a
protected constructor from a context with no access
bar b1{ {0} }; // same
bar b2{ 0 }; // same
bar b3{ {} }; // same
bar b4{}; // by definition, for an aggregate, this is shorthand for b3
The heart of the problem here is that you're using the "bar b{}"
initialization syntax, which has no associated semantics other than "do
what I mean". In this case it's not clear what you mean, and the language
rules say that aggregate initialization wins over calling constructors.
Post by m***@gmail.com
I like to think about the aggregate initialization as a feature that
helps me reduce amount of code that needs to be written to initialize
stuff. IMHO it shouldn't tell user that he uses stuff without access, when
he actually doesn't do that.
To be clear.
I'm not talking about explicit usages of forbidden stuff, e.g.
struct foo
{
int val;
foo(int v = 42) : val{ v } {}
};
struct bar : foo
{};
int main()
{
bar b{{0}};
return b.val;
}
It should not compile for sure.
I'm talking about ctors and members omitted in initializer list, to
which aggregate has access.
P.S.
off the top of my head*
I needed to correct that.
W dniu sobota, 10 listopada 2018 19:05:32 UTC+1 uÅŒytkownik Balog Pal
2018. november 9., péntek 23:42:37 UTC+1 időpontban
I don't see a problem with your examples. You play with access and get
exactly where it follows. In the aggregate-init form, you're in the context
of the function. That function is not friend, so it rejects the attempts to
get to the ctors. If you befriend the function, then it starts working.
In the last example you invoke the (default-generated) ctor of the
class. That can works fine as you did grant friendship to that descendant,
and the function itself does not attempt to work around the protection.
--
---
You received this message because you are subscribed to the Google
Groups "ISO C++ Standard - Discussion" group.
To unsubscribe from this group and stop receiving emails from it, send
Visit this group at
https://groups.google.com/a/isocpp.org/group/std-discussion/.
--
---
You received this message because you are subscribed to the Google Groups
"ISO C++ Standard - Discussion" group.
To unsubscribe from this group and stop receiving emails from it, send an
Visit this group at
https://groups.google.com/a/isocpp.org/group/std-discussion/.
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Discussion" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-discussion+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at https://groups.google.com/a/isocpp.org/group/std-discussion/.
Balog Pal
2018-11-14 15:12:54 UTC
Permalink
Post by m***@gmail.com
Do you think that it's a good idea to prepare a tiny formal paper and post
it to the 'ISO C++ Standard - Future Proposals' group for discussion?

If by formal you mean to properly present the case, as in
- why you think the problematic case appears in the wild and what real
problem it causes
- why the proposed change will prevent that
- ... meanwhile not presenting a variant of the same problem in the other
direction
also with impact analysis that exiting code does not change meaning
silently (or if it does why that risk is bearable)

by all means... I even suggest you to start writing it up, then you can
decide on the way if it worth presenting as in your measure the benefits
outweigh the risks, AND the problem is more worth the committee time than
making all the other features happen and other issues with the standard
fixed.
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Discussion" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-discussion+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at https://groups.google.com/a/isocpp.org/group/std-discussion/.
Continue reading on narkive:
Loading...