Surprising struct initialization

J

Juha Nieminen

I suppose you can never know C++ fully. I would have never guessed
this actually compiles and works:

struct Point { int x, y; };

struct Line
{
Point endpoint[2];
int weight;
};

Line createLine(int sx, int sy, int ex, int ey)
{
Line l = { sx, sy, ex, ey, 1 };
return l;
}

Both gcc and Visual Studio 2005 compile that happily.

My question would be: Is that *really* correct, or are both compilers
simply being lenient? What are the exact rules for the initialization
blocks of structs?
 
P

phlip

Juha said:
I suppose you can never know C++ fully. I would have never guessed
this actually compiles and works:

struct Point { int x, y; };

struct Line
{
Point endpoint[2];
int weight;
};

Line createLine(int sx, int sy, int ex, int ey)
{
Line l = { sx, sy, ex, ey, 1 };
return l;
}

Both gcc and Visual Studio 2005 compile that happily.

My question would be: Is that *really* correct, or are both compilers
simply being lenient? What are the exact rules for the initialization
blocks of structs?

IIRC, if a struct (or an abused class) is a POD, you can aggregate its
construction using classic C notation. POD is Plain Ole' Data - no virtuals,
constants, etc. A POD has an implementation-defined memory layout - and strict
top-to-bottom member locations - so the {,,,} can exploit this to unambiguously
drop data into each slot.
 
G

Greg Herlihy

struct Point { int x, y; };

struct Line
{
    Point endpoint[2];
    int weight;
};

Line createLine(int sx, int sy, int ex, int ey)
{
    Line l = { sx, sy, ex, ey, 1 };
    return l;
}

  Both gcc and Visual Studio 2005 compile that happily.

  My question would be: Is that *really* correct, or are both compilers
simply being lenient? What are the exact rules for the initialization
blocks of structs?

The compilers are not being lenient - multiple braces can be elided in
an aggregate initializer. Therefore, a program can replace this:

Line l = {{{sx, sy}, {ex, ey}}, 1 };

with:

Line l = {sx, sy, ex, ey, 1 };

as long as there are enough arguments to match the aggregate members.
Otherwise, the braces would be needed. For example:

Line l = {{{sx, xy}}, 1}; // l.endpoint[1] is initialized to {0,
0}

I would leave the braces in an aggregate initializer (even when not
needed) just to make the code more readable.

Greg
 
J

James Kanze

I suppose you can never know C++ fully. I would have never
guessed this actually compiles and works:
struct Point { int x, y; };
struct Line
{
Point endpoint[2];
int weight;
};
Line createLine(int sx, int sy, int ex, int ey)
{
Line l = { sx, sy, ex, ey, 1 };
return l;
}

Why shouldn't it?
Both gcc and Visual Studio 2005 compile that happily.
My question would be: Is that *really* correct, or are both
compilers simply being lenient? What are the exact rules for
the initialization blocks of structs?

The same as in C, at least in the case of POD agglomerates. In
the places I've worked, it's generally considered good practice
to put in all of the braces, e.g.:

Line l = { { { sx, sy }, { ex, ey } }, 1 } ;

but the language allows many or all of them to be elided. (The
exact rules are fairly complex, and in a code review, I would
reject any code that elided some without eliding all. But
initializers like the one you present were common in C, and so
are legal in C++ as well.)
 
J

James Kanze

Juha Nieminen <[email protected]> kirjutas:
I suppose you can never know C++ fully. I would have never
guessed this actually compiles and works:
struct Point { int x, y; };
struct Line
{
Point endpoint[2];
int weight;
};
Line createLine(int sx, int sy, int ex, int ey)
{
Line l = { sx, sy, ex, ey, 1 };
return l;
}
This is using the C subset of C++, for both data objects and
initialization, and is perfectly fine as it is. But just add a
ctor to Point or Line to make them non-POD and the
initialization would be illegal.

The exact form he has written would become illegal, but
aggregate initialization is legal for any aggregate, and there's
no problem with something like:

class Point
{
int x ;
int y ;
public:
Point( int a, int b ) ;
// other functions as well...
} ;

struct Line
{
Point endpoint[ 2 ] ;
int weight ;
} ;

Line createLine(int sx, int sy, int ex, int ey)
{
Line l = { Point( sx, sy ), Point( ex, ey ), 1 };
return l;
}

(IIRC, some older versions of VC++ didn't accept this. But that
was a bug in the compiler---it's always been perfectly legal.)
AFAIK the next C++ standard will relax some constraints for
PODness and add some syntax for convenient initialization of
genuine C++ classes as well.

There are several important evolutions concerning
initialization, although I'm not too sure about the details. I
do know, however, that one of the goals was to reduce the
differences in the initialization syntax according to whether
the class was an agglomerate or not.
 
O

Old Wolf

initializers like the one you present were common in C, and so
are legal in C++ as well.)

A minor point; the above initialization is
illegal in C90 which requires initializers
to be constant expressions.
 
J

Juha Nieminen

Old said:
A minor point; the above initialization is
illegal in C90 which requires initializers
to be constant expressions.

I thought that was only the case with array initialization. (Or is it
the other way around nowadays?)
 
O

Old Wolf

I thought that was only the case with array initialization. (Or is it
the other way around nowadays?)

Nowadays (C99) the above is allowed. But in C90
brace-enclosed initializers (for structs and
arrays) had to be constant expressions.
 
G

Greg Herlihy

Nowadays (C99) the above is allowed. But in C90
brace-enclosed initializers (for structs and
arrays) had to be constant expressions.

No, C99 has the same, const-expression initializer requirements for
arrays - as C90 did: namely, that the initializers for arrays with
static storage duration must be constant expressions, whereas the
initializers for arrays with other-than-static storage duration have
no such requirement

So the initializer of "l" array above would be legal (in both C90 and
C99) - just as long as the "l" array is a local variable.

Greg
 
J

James Kanze

No, C99 has the same, const-expression initializer
requirements for arrays - as C90 did: namely, that the
initializers for arrays with static storage duration must be
constant expressions, whereas the initializers for arrays with
other-than-static storage duration have no such requirement

That's what I always thougt too, but on pulling out my old
copy of C90, I find (§6.5.7, in the Constraints section):

All of the expressions in an initializer for an object
that has static storage duration or in an initializer
list for an object that has aggregate or union type
shall be constant expressions.

And in §6.1.2.5, "Array and structure types are collectively
called aggregate types" (with "aggregate types" in italics,
so this is a definition).

Historically, of course, K&R C required constant expressions
for all aggregate initialization, and C99 only requires them
for variables with static lifetime. Apparently, the change
didn't take place until after C90 (although I remember it as
being before).
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,774
Messages
2,569,596
Members
45,135
Latest member
VeronaShap
Top