Topic: Defect Report: Enclosing automatics prohibited in in-class initializers for Local Classes
Author: Faisal Vali <faisalv@gmail.com>
Date: Mon, 27 Apr 2009 01:40:15 CST Raw View
According to section 9.8 of the working paper (n2857) - automatic
names can not be used in declarations within local classes. This
seems like an unnecessary restriction to me, especially when it
concerns their in-class member initializers. For e.g. I feel the
following code (which makes C++ easier to write and behaves exactly as
one would expect) should be well-formed:
for e.g.
void foo(int x, int y)
{
x = 10;
struct Point { int x_ = x; int y_ = y; };
Point p1; // p1.x_ = 10
x = 7;
Point p2; // now p2.x_ = 7
::Call_C_API(&p1);
}
If this restriction is not lifted, there are many workarounds
(supplying a ctor, using a brace-enclosed initializer, but they all
have some issues that prevent them from being entirely seamless) -
also keep in mind that the following can still be done in C++0x - but
once again it is messy, has limitations and feels unnatural:
void static_foo_use(int x, int y)
{
static int s_x, s_y;
s_x = x, s_y = y;
struct Point { int x_ = s_x; int y_ = s_y; };
}
This issue is probably too late to address for C++0x, but I feel it
should be logged as a defect so that it can receive attention when the
time is right.
Of course, the following example does raise some concerns about using
automatics or static locals in initializers that would need to be
addressed (remember, local classes can now be used as template
arguments in C++0x):
template<class T> void use_local_class(T local_class)
{
T t; // what happens here (in the static or automatic case)?
// how hard is this for compilers to get right (in at least
the single threaded case)?
}
Thanks,
Faisal Vali
Radiation Oncology
Loyola
--
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@netlab.cs.rpi.edu]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Author: "Alf P. Steinbach" <alfps@start.no>
Date: Mon, 27 Apr 2009 14:37:30 CST Raw View
* Faisal Vali:
> also keep in mind that the following can still be done in C++0x - but
> once again it is messy, has limitations and feels unnatural:
>
> void static_foo_use(int x, int y)
> {
> static int s_x, s_y;
> s_x = x, s_y = y;
> struct Point { int x_ = s_x; int y_ = s_y; };
> }
Uh, is this feature real?
Someone had the bright idea to not allow floating point constants in
classes,
which there has been consistent demand for since, well, since, and which
can be
emulated by the (extremely verbose) template trick so that one knows
there is no
/technical/ issue, but instead allow the above language level hack-o-rama?
Could Someone please explain (I'm actively moving my hands off the
keyboard so
as to not let them type what I'm thinking as it would be unsuitable here).
Mumble mumble,
- Alf
--
Due to hosting requirements I need visits to <url:
http://alfps.izfree.com/>.
No ads, and there is some C++ stuff! :-) Just going there is good. Linking
to it is even better! Thanks in advance!
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@netlab.cs.rpi.edu]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Author: daniel.kruegler@googlemail.com
Date: Tue, 28 Apr 2009 11:03:23 CST Raw View
On 27 Apr., 09:40, Faisal Vali <fais...@gmail.com> wrote:
> According to section 9.8 of the working paper (n2857) - automatic
> names can not be used in declarations within local classes. This
> seems like an unnecessary restriction to me, especially when it
> concerns their in-class member initializers. For e.g. I feel the
> following code (which makes C++ easier to write and behaves exactly as
> one would expect) should be well-formed:
>
> for e.g.
>
> void foo(int x, int y)
> {
> x = 10;
> struct Point { int x_ = x; int y_ = y; };
> Point p1; // p1.x_ = 10
> x = 7;
> Point p2; // now p2.x_ = 7
Oops - are you sure? I would not expect this
initialization value here. There does not exist
any visual remembrance for me to associate
that changing the value of x also changes the
initialization value of any new Point instance.
> ::Call_C_API(&p1);
> }
>
> If this restriction is not lifted, there are many workarounds
> (supplying a ctor, using a brace-enclosed initializer, but they all
> have some issues that prevent them from being entirely seamless) -
What kind of issues are you implying by rejecting the IMO most
natural brace-enclosed initializer? It seems to me that you need
a POD type, if you want to provide it to a C function. Now let's
consider what shall happen if we want to support
struct Point { int x_ = x; int y_ = y; };
with variables of automatic storage duration with the Point struct.
Does it behave like a lambda-like closure? This might impose
the requirement that Point holds a frame pointer which might
produce some unexpected side effects, if someone tries to
memcpy this beast. It would also lead to the question whether
the user actually wants to copy or to reference the variables
of the surrounding scope - all these questions have been
throughly taken care of for lambda expressions and your
example would raise the question how the capture strategies
are marked for local classes?
> also keep in mind that the following can still be done in C++0x - but
> once again it is messy, has limitations and feels unnatural:
>
> void static_foo_use(int x, int y)
> {
> static int s_x, s_y;
> s_x = x, s_y = y;
> struct Point { int x_ = s_x; int y_ = s_y; };
> }
>
> This issue is probably too late to address for C++0x, but I feel it
> should be logged as a defect so that it can receive attention when the
> time is right.
Note that
http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#696
attempts to lift some restrictions you mention for constants, but
these are not relevant for your example. I mention the issue, because
it's comments explain why local classes are restricted in this
regard.
> Of course, the following example does raise some concerns about using
> automatics or static locals in initializers that would need to be
> addressed (remember, local classes can now be used as template
> arguments in C++0x):
>
> template<class T> void use_local_class(T local_class)
> {
> T t; // what happens here (in the static or automatic case)?
> // how hard is this for compilers to get right (in at least
> the single threaded case)?
> }
Yes, this is another problem, if we have to consider frame
pointers here.
Given your example the most natural solution with currently
existing C++ would be to write
struct Point { int x_; int y_; } p1 = {10, y};
...
Point p2 = {7, y};
I really see no big advantage of your suggestion compared to
the problems it provides. A thing that I really miss from C++0x
is that C99 designators did not come into the language, which
would allow named initialization like this:
Point p2 = {.x_ = 7, .y_ = y};
Greetings from Bremen,
Daniel Kr gler
--
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@netlab.cs.rpi.edu]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Author: daniel.kruegler@googlemail.com
Date: Tue, 28 Apr 2009 12:19:11 CST Raw View
On 27 Apr., 22:37, "Alf P. Steinbach" <al...@start.no> wrote:
> * Faisal Vali:
>
> > also keep in mind that the following can still be done in C++0x - but
> > once again it is messy, has limitations and feels unnatural:
>
> > void static_foo_use(int x, int y)
> > {
> > static int s_x, s_y;
> > s_x = x, s_y = y;
> > struct Point { int x_ = s_x; int y_ = s_y; };
> > }
>
> Uh, is this feature real?
>
> Someone had the bright idea to not allow floating point constants in
> classes,
> which there has been consistent demand for since, well, since, and which
> can be
> emulated by the (extremely verbose) template trick so that one knows
> there is no
> /technical/ issue, but instead allow the above language level hack-o-rama?
>
> Could Someone please explain (I'm actively moving my hands off the
> keyboard so
> as to not let them type what I'm thinking as it would be unsuitable here).
>
> Mumble mumble,
I'm not sure whether I have correctly understood your question or not:
Are you mumbling that the syntax
struct Point { int x_ = s_x; int y_ = s_y; };
is valid *at all* given some constraints on s_x/s_y or are you
mumbling
that these constraints are *unnecessarily strong*?
The new /brace-or-equal-initializer/ grammar extension was provided
to prevent unnecessary repetitions of initializing values for specific
class members. While the also new constructor delegation is very
good in simplifying several use-cases there are still some which
cannot be easily solved with this. A good example is given in the
extension proposal paper
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2756.htm
From this spec it's clear that the semantics of this in-class
initializer
must be similar to those in the actual c'tor initializer list: This
allows
initialization with already initialized non-static data members or
initialization via static data members. Except for the shift of
locality
in regard to the constructor itself there is nothing new and does not
introduce any hidden addition of a "static scope pointer".
Now the proposal of Faisal Vali seems to cause that such a
static scope pointer is introduced into local classes (at least
potentially) and this is not easy to recognize from the source
code. His key example seems similar easy to solve with a
brace initializer which would not transform the normal pod type
Point to a silently potentially no-longer-pod type. A more severe
problem of that extension is that it would also unexpectedly
increase the data structure size, which would break the layout
guarantees for these special types, see e.g. 9.2 [class.mem]/15:
"Two standard-layout struct (Clause 9) types are layout-compatible
if they have the same number of non-static data members and
corresponding non-static data members (in declaration order) have
layout-compatible types (3.9)."
or p.18 of the same section:
"A pointer to a standard-layout struct object, suitably converted
using a reinterpret_cast, points to its initial member (or if that
member is a bit-field, then to the unit in which it resides) and
vice versa.[..]"
Greetings from Bremen,
- Daniel
--
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@netlab.cs.rpi.edu]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Author: Faisal Vali <faisalv@gmail.com>
Date: Wed, 29 Apr 2009 01:02:06 CST Raw View
On Apr 28, 12:03 pm, daniel.krueg...@googlemail.com wrote:
> On 27 Apr., 09:40, Faisal Vali <fais...@gmail.com> wrote:
>
>
>
<snip>
> > void foo(int x, int y)
> > {
> > x = 10;
> > struct Point { int x_ = x; int y_ = y; };
> > Point p1; // p1.x_ = 10
> > x = 7;
> > Point p2; // now p2.x_ = 7
>
> Oops - are you sure? I would not expect this
> initialization value here. There does not exist
> any visual remembrance for me to associate
> that changing the value of x also changes the
> initialization value of any new Point instance.
Well I'll be honest, I'm not sure if it's the best idea - but it would
make some of my code pithier ;)
The visual remembrance argument is a good one, but keep in mind that
there is no visual remembrance for a static or global var used as an
initializer (and then changed later) either.
>
> > ::Call_C_API(&p1);
> > }
>
> > If this restriction is not lifted, there are many workarounds
> > (supplying a ctor, using a brace-enclosed initializer, but they all
> > have some issues that prevent them from being entirely seamless) -
>
> What kind of issues are you implying by rejecting the IMO most
> natural brace-enclosed initializer?
void do_something_with_window( Handle h )
{
struct Local
{
Resource* r1 = new Resource();
Resource* r2;
Handle h1 = h; // i'd like to write this, instead of creating a
param in my ctor
Local(int i) { r2 = new Resource(i); }
Local(Local&& l) : r1(l.r1), r2(l.r2) { l.r1 = l.r2 = nullptr; }
~Local() { delete r1; delete r2; }
void operator()() { ... }
};
Thread t(Local(RESOURCE_ID));
}
It saves a little bit of typing since i don't have to duplicate the
typename (Handle) in the ctor parameter list - and i don't have to
remove the ctor parameter and mem initializer if all i want to do is
to comment that member out temporarily. This syntax doesn't work with
brace-enclosed initializers for Local;
> Now let's
> consider what shall happen if we want to support
>
> struct Point { int x_ = x; int y_ = y; };
>
> with variables of automatic storage duration with the Point struct.
>
> Does it behave like a lambda-like closure?
Yes, in some sense, since the compiler would have to keep track of the
frame pointer.
But Local classes are different enough that not all their advantages
can be simulated using lambdas (no user defined ctors or dtors in
lambdas).
<snip>
>
> Note that
>
> http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#696
> I mention the issue, because
> it's comments explain why local classes are restricted in this
> regard.
Thanks - that was useful indeed :)
<snip>
> I really see no big advantage of your suggestion compared to
> the problems it provides.
You could be right here - it provides a convenience that i think is
significant - but probably not significant enough to have compilers
associate frame pointers with local classes and complicate their
layouts.
> A thing that I really miss from C++0x
> is that C99 designators did not come into the language, which
> would allow named initialization like this:
>
> Point p2 = {.x_ = 7, .y_ = y};
I agree with you completely here - this would have been a very useful
addition - hopefully we can put something together for the next C++
revision (post C++0x).
regards,
Faisal Vali
Radiation Oncology
Loyola
--
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@netlab.cs.rpi.edu]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Author: daniel.kruegler@googlemail.com
Date: Wed, 29 Apr 2009 22:25:00 CST Raw View
On Apr 29, 9:02 am, Faisal Vali <fais...@gmail.com> wrote:
> On Apr 28, 12:03 pm, daniel.krueg...@googlemail.com wrote:
>
> > On 27 Apr., 09:40, Faisal Vali <fais...@gmail.com> wrote:
>
> <snip>
> > > void foo(int x, int y)
> > > {
> > > x = 10;
> > > struct Point { int x_ = x; int y_ = y; };
> > > Point p1; // p1.x_ = 10
> > > x = 7;
> > > Point p2; // now p2.x_ = 7
>
> > Oops - are you sure? I would not expect this
> > initialization value here. There does not exist
> > any visual remembrance for me to associate
> > that changing the value of x also changes the
> > initialization value of any new Point instance.
>
> Well I'll be honest, I'm not sure if it's the best idea - but it would
> make some of my code pithier ;)
>
> The visual remembrance argument is a good one, but keep in mind that
> there is no visual remembrance for a static or global var used as an
> initializer (and then changed later) either.
I wouldn't weight this counter argument too strong, because it
simply reflects the nature of "global" variables. In your work-
around this is a local one, but still with static storage duration,
so essentially quite similar to the first kind. Even without
any C++0x features Meyer's singleton just takes advantage
of this global-like nature of local statics.
Another problem of the work-around is, that it is not
thread-safe, so I wouldn't recommend it.
> > What kind of issues are you implying by rejecting the IMO most
> > natural brace-enclosed initializer?
>
> void do_something_with_window( Handle h )
> {
> struct Local
> {
> Resource* r1 = new Resource();
> Resource* r2;
> Handle h1 = h; // i'd like to write this, instead of creating a
> param in my ctor
> Local(int i) { r2 = new Resource(i); }
> Local(Local&& l) : r1(l.r1), r2(l.r2) { l.r1 = l.r2 = nullptr; }
> ~Local() { delete r1; delete r2; }
> void operator()() { ... }
> };
> Thread t(Local(RESOURCE_ID));
> }
>
> It saves a little bit of typing since i don't have to duplicate the
> typename (Handle) in the ctor parameter list - and i don't have to
> remove the ctor parameter and mem initializer if all i want to do is
> to comment that member out temporarily. This syntax doesn't work with
> brace-enclosed initializers for Local;
Lines marked with # have been modified:
void do_something_with_window( Handle h )
{
struct Local
{
Resource* r1 = new Resource();
Resource* r2;
Handle h1; // #1
Local(int i, Handle h) { r2 = new Resource(i); h1 = h; } // #2
Local(Local&& l) : r1(l.r1), r2(l.r2), h1(l.h1) { l.r1 = l.r2 =
l.h1 = nullptr; } // #3
~Local() { delete r1; delete r2; }
void operator()() { ... }
};
Thread t(Local(RESOURCE_ID, h)); // #4
}
The construction syntax "Local(RESOURCE_ID, h)" can
be replaced by Local{RESOURCE_ID, h}, because of
C++0x's unified initialization syntax. Or use
Local loc{RESOURCE_ID, h};
for a named variable, if you like.
To me the revised move constructor seems to do what
it should do (in contrast to your own writing above). The
"welldefined-ness" of your code above would strongly
depend on the question whether a frame pointer would
be used or whether the Handle would be copied, because
by starting the new thread your Local instance will be
moved into the thread object. If *inside* the thread
object (after starting the thread) another move happens,
this would try to copy the now invalid reference of the
previous Handle instance (local to
do_something_with_window) to the moved instance
(this follows from the rules: Since h1 is not mentioned
in the move c'tor initializer list, it will invoke the
Handle h1 = h;
initialization). Any usage that is not a pure writing
usage of this member causes undefined behavior.
This seems to me to be a high price for the win of
one less explicitly mentioned c'tor argument.
Your argument that you would like to experiment
whether Handle is actually needed in the local class
is difficult to argue about without having an idea
what is actually done with the handle inside the
Local class - the immediate question would be
how the internals of Local handle the optional
existence of this member at all. If it exists just
for debugging purposes, the actual member
Handle could be replaced by a HandleHolder
struct HandleHolder {
Handle h; // Remove this member, if not needed
HandleHolder(Handle h) : h(h) {}
};
and your code in Local would remain untouched
of this refactoring of HandleHolder, because all
c'tors would only refer to HandleHolder instead.
Greetings from Bremen,
Daniel Kr gler
--
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@netlab.cs.rpi.edu]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Author: Faisal Vali <faisalv@gmail.com>
Date: Sun, 3 May 2009 10:11:36 CST Raw View
> On Apr 29, 11:25 pm, daniel.krueg...@googlemail.com wrote:
> > On Apr 29, 9:02 am, Faisal Vali <fais...@gmail.com> wrote:
>
>
>
<snip>
> > The visual remembrance argument is a good one, but keep in mind that
> > there is no visual remembrance for a static or global var used as an
> > initializer (and then changed later) either.
>
> I wouldn't weight this counter argument too strong, because it
> simply reflects the nature of "global" variables. In your work-
> around this is a local one, but still with static storage duration,
> so essentially quite similar to the first kind.
I don't agree with your reasoning here - while i agree that local
static variables can have similar lifetimes to globals - by your
rationale autos created in 'main' would have similar life-times to
local statics and thus the visual remembrance argument should not
hold. I find something inconsistent about that. The visual
remembrance argument is a good one, and it is as good an argument
against the use of local statics in this context as it is against the
use of local autos. Allowing one and not the other is inconsistent.
Anyway, I do agree that it is easier to complicate one's life by
implicitly passing frame pointers around versus passing pointers to
objects of static duration - but then i feel there should be a way to
use auto initialization without having to pass frame pointers around
(i.e use the actual address of the auto variable directly, instead of
its offset to the frame pointer - and if you use it once its life time
is over - well, that's too bad, but then this problem is one that C++
programmers are generally aware of, especially when passing around
pointers/refs to auto variables).
<snip>
> Another problem of the work-around is, that it is not
> thread-safe, so I wouldn't recommend it.
Yes - like i said, every work-around has issues - and my intention was
not to recommend the work around but to show that compromising code
would be written is using these work arounds.
>
>
>
> > > What kind of issues are you implying by rejecting the IMO most
> > > natural brace-enclosed initializer?
>
<snip>
> Lines marked with # have been modified:
>
> void do_something_with_window( Handle h )
> {
> struct Local
> {
> Resource* r1 = new Resource();
> Resource* r2;
> Handle h1; // #1
> Local(int i, Handle h) { r2 = new Resource(i); h1 = h; } // #2
> Local(Local&& l) : r1(l.r1), r2(l.r2), h1(l.h1) { l.r1 = l.r2 =
> l.h1 = nullptr; } // #3
> ~Local() { delete r1; delete r2; }
> void operator()() { ... }
> };
> Thread t(Local(RESOURCE_ID, h)); // #4
>
> }
>
> The construction syntax "Local(RESOURCE_ID, h)" can
> be replaced by Local{RESOURCE_ID, h}, because of
> C++0x's unified initialization syntax. Or use
>
> Local loc{RESOURCE_ID, h};
>
> for a named variable, if you like.
Like i said, this involves introducing a parameter into the ctor and i
would like to avoid that. In the initial stages of development, there
is much about the enclosing function that can change (i.e num, name
and type of parameters and local vars etc.) and each of those changes
can ripple (somewhat amplified) into the local class if the typename
of that parameter or auto variable is explicitly mentioned in the
ctor.
>
> To me the revised move constructor seems to do what
> it should do (in contrast to your own writing above).
Yes, I should have included l.h in my move ctor - an oversight. A
minor typing addition.
<snip>
>
> Your argument that you would like to experiment
> whether Handle is actually needed in the local class
> is difficult to argue about without having an idea
> what is actually done with the handle inside the
> Local class - the immediate question would be
> how the internals of Local handle the optional
> existence of this member at all. If it exists just
> for debugging purposes, the actual member
> Handle could be replaced by a HandleHolder
>
> struct HandleHolder {
> Handle h; // Remove this member, if not needed
> HandleHolder(Handle h) : h(h) {}
>
> };
>
<snip>
Yes, like i said, there are many workarounds and they all have some
issues that require additional (redundant boiler plate) typing (such
as your solution).
I'm not convinced that adding this feature (use of autos for in-class
initialization in local classes) should necessarily complicate the
layouts of local classes (i.e. it is not clear to me why frame
pointers need to be sttached versus the direct use of the address of
the auto variable).
Also, all the arguments raised against the use of autos in this
"risky" fashion can be raised against the use of passing around
pointers to auto variables. The visual remembrance (changing one
object alters the state/initialization of another object, without
there being a clear link to remind us of this) argument applies
equally to local statics in this context, and in general, I feel that C
++ programmers once warned about this, should be able to pay
sufficient attention to such issues (just like they do with pointers
and aliasing).
So considering the arguments that have been made so far in this
thread, I maintain my position (the advantages of using autos for in-
class initialization outweigh the disadvantages - and they work in
quite a predictable fashion).
regards,
Faisal Vali
Radiation Oncology
Loyola
--
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@netlab.cs.rpi.edu]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]