Initializing a malloc'ed struct whose fields are const with run-timevalues

B

Ben Bacarisse

Shao Miller said:
Well I was hopeful for #3, but seemed to recall having previously read
some other bit of Standard text contradicting it. When I went
looking, I came across 6.7.3p9 and it looked like it was the
antagonist. Perhaps that wasn't it, after all.


Well I was thinking along the lines of the qualification of the return
type's type category, but maybe not...

I don't know what a type's type category is so I can't comment here.
Have you any thoughts about #1, #2, #3?

The return type is the same as that of the function so no conversion
takes place. The value of res is the value returned. It does not seem
to be complicated to me.
 
N

Noob

BartC said:
Where should it load them from instead?

I expected the compiler to write the value to a register before
the loop, and read it from there henceforth.
Perhaps there was no opportunity to keep the values resident in registers.

If I convince the compiler that "i" will not change during the
lifetime of the function by defining a copy on the stack:

const int i = ctx->i;
while ( 1 ) { use i, and the rest of the ctx }

then i is indeed kept in a register (of which I have 32 on this
platform). I was hoping to sprinkle const-qualifiers in the struct
definition so that I wouldn't have to make copies of every const
field in the ctx struct.

Regards.
 
B

Ben Bacarisse

Noob said:
I expected the compiler to write the value to a register before
the loop, and read it from there henceforth.


If I convince the compiler that "i" will not change during the
lifetime of the function by defining a copy on the stack:

const int i = ctx->i;
while ( 1 ) { use i, and the rest of the ctx }

then i is indeed kept in a register (of which I have 32 on this
platform). I was hoping to sprinkle const-qualifiers in the struct
definition so that I wouldn't have to make copies of every const
field in the ctx struct.

You may find that using restrict (if appropriate) gives better results
than const. For example, given

struct toto { int i; double d; };

double f(struct toto *ctx, double *p)
{
double s = 0;
for (int i = 0; i < ctx->i; i++) {
s += ctx->d;
*p += 1;
}
return s;
}

gcc won't keep ctx->d in a register because *p might alias it. The code
loads ctx->d every time even if cts points to a const struct with const
members. But making both ctx and p restrict qualified does what you want.
 
N

Noob

Ben said:
You may find that using restrict (if appropriate) gives better results
than const. For example, given

struct toto { int i; double d; };

double f(struct toto *ctx, double *p)
{
double s = 0;
for (int i = 0; i < ctx->i; i++) {
s += ctx->d;
*p += 1;
}
return s;
}

gcc won't keep ctx->d in a register because *p might alias it. The code
loads ctx->d every time even if cts points to a const struct with const
members. But making both ctx and p restrict qualified does what you want.

I could be wrong, but I don't think this is an aliasing problem.

The prototype for the thread's entry point is
void *start_routine(void *arg)
(In my case, arg is, in fact, a (struct toto *))

To make your example more like mine, your function "f" should only take
a single (struct toto *) parameter, in which case I'm not sure restrict-
qualifying the parameter brings much? (And is it compatible with the
expected prototype?)

Does it make sense to restrict-qualify struct fields?

I'm off to browse ISO/IEC 9899:TC3

Regards.
 
B

Ben Bacarisse

Noob said:
I could be wrong, but I don't think this is an aliasing problem.

Quite. I was just punting -- it may well not be an aliasing problem.
The prototype for the thread's entry point is
void *start_routine(void *arg)
(In my case, arg is, in fact, a (struct toto *))

To make your example more like mine, your function "f" should only take
a single (struct toto *) parameter, in which case I'm not sure restrict-
qualifying the parameter brings much?

I think not, but it does depend on other pointers of course, like global
ones. With C11 (which has a threading model) things may be different.
It's not an area I have really studied.
(And is it compatible with the expected prototype?)

No, but you can always arrange for the entry-point to call another
function with your desired prototype.
Does it make sense to restrict-qualify struct fields?

Absolutely. It is possible that the compiler is having to assume
aliasing through one or more of these?
 
S

Shao Miller

Tim Rentsch said:
Ian Collins said:
[snip]

I just tried this code:

#include <stdio.h>

struct toto { const int i; const float f; const void *p; };

struct toto foo(int i, float f, void *p)
{
return (struct toto){i, f, p};
}

int main(void)
{
float f = foo( 1,2,NULL).f;
}

and gcc (-Wall -std=c99 -pedantic) compiles it, but Sun c99 (which
tends to be both strict and conforming) rejects it with one error:

"x.c", line 7: left operand must be modifiable lvalue: op "="

where line 7 is the function return.

I believe gcc has it right here. Even the error message from
the Sun compiler suggests they were thinking of this like a
kind of assignment, which it certainly isn't in this case.

As you say elsewhere, the assignment-like nature of the return only
kicks in when the types differ, but even when it does, I'd take "is
converted as if by assignment" to mean that only the conversion and type
constraints of assignment are to be considered. (The constraints being
important because they would, for example, prevent a function with type
'T *' returning a 'const T * value without a diagnostic).

More curious to me is that gcc (in conforming mode) permits this:

struct toto { const int i; const float f; const void *p; };

struct toto foo(int i, float f, void *p)
{
return (struct toto){i, f, p};
}

int bar(struct toto p) { return p.i; }

int main(void)
{
return bar(foo(1, 2, 0));
}

but 6.5.2.2 p2 says:

"Each argument shall have a type such that its value may be assigned
to an object with the unqualified version of the type of its
corresponding parameter."

The phrase "unqualified version of the type" can't surely be intended to
mean the recursive removal of all type qualifiers. Elsewhere it simply
means that the "top level" qualifiers are removed. If that's the right
interpretation, the call to bar should be a constraint violation.

Why? The result of the call to 'foo' yields a 'struct toto', which is
not qualified (and also isn't an lvalue).
 
S

Shao Miller

I expected the compiler to write the value to a register before
the loop, and read it from there henceforth.


If I convince the compiler that "i" will not change during the
lifetime of the function by defining a copy on the stack:

const int i = ctx->i;
while ( 1 ) { use i, and the rest of the ctx }

then i is indeed kept in a register (of which I have 32 on this
platform). I was hoping to sprinkle const-qualifiers in the struct
definition so that I wouldn't have to make copies of every const
field in the ctx struct.

I think it depends what's in the loop body. Is there any chance of
sharing it? I was doing a bit of thinking about aliasing recently, so
maybe something applies.
 
I

Ian Collins

Shao said:
Tim Rentsch said:
[snip]

I just tried this code:

#include <stdio.h>

struct toto { const int i; const float f; const void *p; };

struct toto foo(int i, float f, void *p)
{
return (struct toto){i, f, p};
}

int main(void)
{
float f = foo( 1,2,NULL).f;
}

and gcc (-Wall -std=c99 -pedantic) compiles it, but Sun c99 (which
tends to be both strict and conforming) rejects it with one error:

"x.c", line 7: left operand must be modifiable lvalue: op "="

where line 7 is the function return.

I believe gcc has it right here. Even the error message from
the Sun compiler suggests they were thinking of this like a
kind of assignment, which it certainly isn't in this case.

As you say elsewhere, the assignment-like nature of the return only
kicks in when the types differ, but even when it does, I'd take "is
converted as if by assignment" to mean that only the conversion and type
constraints of assignment are to be considered. (The constraints being
important because they would, for example, prevent a function with type
'T *' returning a 'const T * value without a diagnostic).

More curious to me is that gcc (in conforming mode) permits this:

struct toto { const int i; const float f; const void *p; };

struct toto foo(int i, float f, void *p)
{
return (struct toto){i, f, p};
}

int bar(struct toto p) { return p.i; }

int main(void)
{
return bar(foo(1, 2, 0));
}

but 6.5.2.2 p2 says:

"Each argument shall have a type such that its value may be assigned
to an object with the unqualified version of the type of its
corresponding parameter."

The phrase "unqualified version of the type" can't surely be intended to
mean the recursive removal of all type qualifiers. Elsewhere it simply
means that the "top level" qualifiers are removed. If that's the right
interpretation, the call to bar should be a constraint violation.

Why? The result of the call to 'foo' yields a 'struct toto', which is
not qualified (and also isn't an lvalue).

I think Ben's point is the *parameter* value may be *assigned*, not the
result of the expression.

I see Sun c99, which rejects the return in foo, also accepts bar.
 
S

Shao Miller

Shao said:
[snip]

I just tried this code:

#include <stdio.h>

struct toto { const int i; const float f; const void *p; };

struct toto foo(int i, float f, void *p)
{
return (struct toto){i, f, p};
}

int main(void)
{
float f = foo( 1,2,NULL).f;
}

and gcc (-Wall -std=c99 -pedantic) compiles it, but Sun c99 (which
tends to be both strict and conforming) rejects it with one error:

"x.c", line 7: left operand must be modifiable lvalue: op "="

where line 7 is the function return.

I believe gcc has it right here. Even the error message from
the Sun compiler suggests they were thinking of this like a
kind of assignment, which it certainly isn't in this case.

As you say elsewhere, the assignment-like nature of the return only
kicks in when the types differ, but even when it does, I'd take "is
converted as if by assignment" to mean that only the conversion and type
constraints of assignment are to be considered. (The constraints being
important because they would, for example, prevent a function with type
'T *' returning a 'const T * value without a diagnostic).

More curious to me is that gcc (in conforming mode) permits this:

struct toto { const int i; const float f; const void *p; };

struct toto foo(int i, float f, void *p)
{
return (struct toto){i, f, p};
}

int bar(struct toto p) { return p.i; }

int main(void)
{
return bar(foo(1, 2, 0));
}

but 6.5.2.2 p2 says:

"Each argument shall have a type such that its value may be assigned
to an object with the unqualified version of the type of its
corresponding parameter."

The phrase "unqualified version of the type" can't surely be intended to
mean the recursive removal of all type qualifiers. Elsewhere it simply
means that the "top level" qualifiers are removed. If that's the right
interpretation, the call to bar should be a constraint violation.

Why? The result of the call to 'foo' yields a 'struct toto', which is
not qualified (and also isn't an lvalue).

I think Ben's point is the *parameter* value may be *assigned*, not the
result of the expression.

I see Sun c99, which rejects the return in foo, also accepts bar.

As in, a constraint violation against 6.5.16p2? If so, why would that
constraint apply? There are still constraints in 6.5.16.1p1... As
asked elsethread, why pick out "modifiable" and not "lvalue"? Do we
need to worry about the constraints and semantics of 6.5.16.2? Isn't
backing up to 6.5.16 backing up too far?
 
B

Ben Bacarisse

Ian Collins said:
Shao said:
[snip]

I just tried this code:

#include <stdio.h>

struct toto { const int i; const float f; const void *p; };

struct toto foo(int i, float f, void *p)
{
return (struct toto){i, f, p};
}

int main(void)
{
float f = foo( 1,2,NULL).f;
}

and gcc (-Wall -std=c99 -pedantic) compiles it, but Sun c99 (which
tends to be both strict and conforming) rejects it with one error:

"x.c", line 7: left operand must be modifiable lvalue: op "="

where line 7 is the function return.

I believe gcc has it right here. Even the error message from
the Sun compiler suggests they were thinking of this like a
kind of assignment, which it certainly isn't in this case.

As you say elsewhere, the assignment-like nature of the return only
kicks in when the types differ, but even when it does, I'd take "is
converted as if by assignment" to mean that only the conversion and type
constraints of assignment are to be considered. (The constraints being
important because they would, for example, prevent a function with type
'T *' returning a 'const T * value without a diagnostic).

More curious to me is that gcc (in conforming mode) permits this:

struct toto { const int i; const float f; const void *p; };

struct toto foo(int i, float f, void *p)
{
return (struct toto){i, f, p};
}

int bar(struct toto p) { return p.i; }

int main(void)
{
return bar(foo(1, 2, 0));
}

but 6.5.2.2 p2 says:

"Each argument shall have a type such that its value may be assigned
to an object with the unqualified version of the type of its
corresponding parameter."

The phrase "unqualified version of the type" can't surely be intended to
mean the recursive removal of all type qualifiers. Elsewhere it simply
means that the "top level" qualifiers are removed. If that's the right
interpretation, the call to bar should be a constraint violation.

Why? The result of the call to 'foo' yields a 'struct toto', which is
not qualified (and also isn't an lvalue).

I think Ben's point is the *parameter* value may be *assigned*, not
the result of the expression.

Yes, the types are fine but a value of stuct foo type can't be assigned
to an object of that type. It reads as though parameter passing is,
essentially, assignment and whilst you can initialise a struct toto
object, you can't assign to one.

[I've just snipped a long and tedious paragraph trying to give some other
meaning to the phrasing, but since I don't buy it seems pointless to punt
it.]
I see Sun c99, which rejects the return in foo, also accepts bar.

Interesting. Is it also a C++ compiler when given other options? C++
uses initialisation semantics for parameter passing, so it might be an
accident. Of course it could just be that the gcc and Sun c99 authors
are just better as reading standards!
 
I

Ian Collins

Ben said:
Interesting. Is it also a C++ compiler when given other options? C++
uses initialisation semantics for parameter passing, so it might be an
accident. Of course it could just be that the gcc and Sun c99 authors
are just better as reading standards!

No, I'm sure the compilers have a different front end, their error
messages have a different style. For example with

struct toto t;
t = foo( 1,2,NULL);

c99 reports

"x.c", line 17: left operand must be modifiable lvalue: op "="

while CC reports

"x.c", line 16: Error: t must be initialized.
"x.c", line 17: Error: toto has a const member i and cannot be assigned.
 
I

Ian Collins

Shao said:
As in, a constraint violation against 6.5.16p2?
Yes.

If so, why would that constraint apply?

Because of 6.5.2.2 p4, if you take the word "assigned" literally.
 
B

Ben Bacarisse

Ian Collins said:
No, I'm sure the compilers have a different front end, their error
messages have a different style.

Ah, right. Thanks. Just another data point: Comeau's online C99
compiler accepts it (both the return and the call of 'bar').

<snip>
 
T

Tim Rentsch

Noob said:
I could be wrong, but I don't think this is an aliasing
problem.

This comment seems to me peculiar. Do you mean you think there
isn't any aliasing going on, or that you think the compiler is
smart enough to realize there isn't any aliasing going on? There
is a big difference between those two circumstances.
The prototype for the thread's entry point is
void *start_routine(void *arg)
(In my case, arg is, in fact, a (struct toto *))

To make your example more like mine, your function "f" should
only take a single (struct toto *) parameter, in which case I'm
not sure restrict-qualifying the parameter brings much? (And
is it compatible with the expected prototype?)

If you have a function declared, for example, as

void * blah( struct whatever * );

that is compatible with defining the function thusly

void *
blah( struct whatever * restrict it ){ ... }

because of how top-level qualifiers are treated for function
parameters.
Does it make sense to restrict-qualify struct fields?

Unlikely. Probably not impossible, but almost certainly
not a path that will help solve the problem you are
interested in solving here.
 
S

Shao Miller

Because of 6.5.2.2 p4, if you take the word "assigned" literally.

Did you mean if you _don't_ take it literally? I don't see "assigned"
as a defined term, so I assume it's plain English. The Committee
Response for Defect Report #212 includes "assigned" when their code
shows initialization.

Considering 6.9.1p11, does its use of "assigned" make sense if one of
the parameters was 'const'-qualified? I don't think so.

6.7.3p4:

"The properties associated with qualified types are meaningful only
for expressions that are lvalues.132)"

The "object" in "object with the unqualified version of the type of its
corresponding parameter" seems to me to be a theoretical object and not
an lvalue. It's not the parameter, for example, because the parameter
could have a different type. So I don't grok a constraint violation
against 6.5.16p2.
 
T

Tim Rentsch

Ben Bacarisse said:
Tim Rentsch said:
Ian Collins said:
[snip]

I just tried this code:

#include <stdio.h>

struct toto { const int i; const float f; const void *p; };

struct toto foo(int i, float f, void *p)
{
return (struct toto){i, f, p};
}

int main(void)
{
float f = foo( 1,2,NULL).f;
}

and gcc (-Wall -std=c99 -pedantic) compiles it, but Sun c99
(which tends to be both strict and conforming) rejects it
with one error:

"x.c", line 7: left operand must be modifiable lvalue: op "="

where line 7 is the function return.

I believe gcc has it right here. Even the error message from
the Sun compiler suggests they were thinking of this like a
kind of assignment, which it certainly isn't in this case.

As you say elsewhere, the assignment-like nature of the return
only kicks in when the types differ, but even when it does, I'd
take "is converted as if by assignment" to mean that only the
conversion and type constraints of assignment are to be
considered.

I assume you mean >simple< assignment in the last part of that
sentence.
(The constraints being important because they would, for example,
prevent a function with type 'T *' returning a 'const T * value
without a diagnostic).

I expect you're right if we're talking about what meaning was
intended. However, just in terms of what is written, it's hard
to make that stand up considering other parts of the Standard.
In both of the other two cases (those being function arguments
and initializer expressions) where there are similar concerns,
the limiting constraints are identified explicitly. So their
absence for 'return' stands out -- exceptio probat regulam in
casibus non exceptis, and all that. In all likelihood though
that was just an oversight, and a reasonable inference for
purposes of comp.lang.c is the constraints of simple assignment
are meant to apply.

More curious to me is that gcc (in conforming mode) permits
this:

struct toto { const int i; const float f; const void *p; };

struct toto foo(int i, float f, void *p)
{
return (struct toto){i, f, p};
}

int bar(struct toto p) { return p.i; }

int main(void)
{
return bar(foo(1, 2, 0));
}

but 6.5.2.2 p2 says:

"Each argument shall have a type such that its value may be
assigned to an object with the unqualified version of the
type of its corresponding parameter."

The phrase "unqualified version of the type" can't surely be
intended to mean the recursive removal of all type qualifiers.
Elsewhere it simply means that the "top level" qualifiers are
removed. If that's the right interpretation, the call to bar
should be a constraint violation.

[speculation about why gcc acts as it does] Anyway, either
I'm misreading something or gcc has missed something.

Yes, your analysis looks spot on.

Here though I think we're looking at the flip side of the coin.
That is, this case also represents an oversight, only here the
result is more restrictive rather than less restrictive. It
seems unlikely that this tiny incompatibility would knowingly be
imposed just for function arguments, especially since it is
clearly a useful capability to support, and initialization
semantics are so intuitively natural. I expect it was just an
accident of how the writing happened -- provisions were put in to
handle the common cases, and this one unusual case fell through
the cracks. (And you can see how someone reading the rules for
function arguments might gloss over the distinction between the
two ways "unqualified version of the type" might be read.) In
a way it's good to see that common sense has prevailed (ie, in
gcc, and also I think another compiler was mentioned in another
posting) about what was meant here. All that remains is making
sure what's in the Standard gets updated to reflect that.
 
S

Shao Miller

Here though I think we're looking at the flip side of the coin.
That is, this case also represents an oversight, only here the
result is more restrictive rather than less restrictive. It
seems unlikely that this tiny incompatibility would knowingly be
imposed just for function arguments, especially since it is
clearly a useful capability to support, and initialization
semantics are so intuitively natural. I expect it was just an
accident of how the writing happened -- provisions were put in to
handle the common cases, and this one unusual case fell through
the cracks. (And you can see how someone reading the rules for
function arguments might gloss over the distinction between the
two ways "unqualified version of the type" might be read.) In
a way it's good to see that common sense has prevailed (ie, in
gcc, and also I think another compiler was mentioned in another
posting) about what was meant here. All that remains is making
sure what's in the Standard gets updated to reflect that.

I don't see how you can reconcile a 'const'-qualified parameter with
these two paragraphs of 6.9.1, if the "modifiable lvalue" from 6.5.16p2
has any bearing:

"10 On entry to the function, the size expressions of each variably
modified parameter are evaluated and the value of each argument
expression is converted to the type of the corresponding parameter as if
by assignment. (Array expressions and function designators as arguments
were converted to pointers before the call.)

11 After all parameters have been assigned, the compound statement
that constitutes the body of the function definition is executed."
 
K

Keith Thompson

Ben Bacarisse said:
Nick Bowler said:
BTW, in C99 you don't even need a declaration:

if (res != NULL) memcpy(res, &(struct toto){i, f, p}, sizeof *res);

Danger, Will Robinson!

Implementations are allowed to define a function-like macro called
memcpy, and the above line will not work on implementations that do so.
Macro rguments will be split on the commas in the compound literal,
which is almost certainly not desirable. [...]
if (res != NULL) memcpy(res, (&(struct toto){i, f, p}), sizeof *res);
if (res != NULL) (memcpy)(res, &(struct toto){i, f, p}, sizeof *res);

I'd choose the first (ugly though it is) just because the macro might
well be there because it provides some benefit.

There might have been an argument for counting {} along with ()s when
collecting, well, arguments but it's too late now...

That could have broken existing code. A shamelessly contrived example:

#include <stdio.h>
#define WRAP(left, middle, right) left middle right
int main(void)
WRAP({, printf("Hello, world\n");, })

I can't think of any non-contrived use cases, but there could be some.
 
B

Ben Bacarisse

Tim Rentsch said:
Ben Bacarisse <[email protected]> writes:
More curious to me is that gcc (in conforming mode) permits
this:

struct toto { const int i; const float f; const void *p; };

struct toto foo(int i, float f, void *p)
{
return (struct toto){i, f, p};
}

int bar(struct toto p) { return p.i; }

int main(void)
{
return bar(foo(1, 2, 0));
}

but 6.5.2.2 p2 says:

"Each argument shall have a type such that its value may be
assigned to an object with the unqualified version of the
type of its corresponding parameter."

The phrase "unqualified version of the type" can't surely be
intended to mean the recursive removal of all type qualifiers.
Elsewhere it simply means that the "top level" qualifiers are
removed. If that's the right interpretation, the call to bar
should be a constraint violation.

[speculation about why gcc acts as it does] Anyway, either
I'm misreading something or gcc has missed something.

Yes, your analysis looks spot on.

Here though I think we're looking at the flip side of the coin.
That is, this case also represents an oversight, only here the
result is more restrictive rather than less restrictive. It
seems unlikely that this tiny incompatibility would knowingly be
imposed just for function arguments, especially since it is
clearly a useful capability to support, and initialization
semantics are so intuitively natural. I expect it was just an
accident of how the writing happened -- provisions were put in to
handle the common cases, and this one unusual case fell through
the cracks. (And you can see how someone reading the rules for
function arguments might gloss over the distinction between the
two ways "unqualified version of the type" might be read.) In
a way it's good to see that common sense has prevailed (ie, in
gcc, and also I think another compiler was mentioned in another
posting) about what was meant here. All that remains is making
sure what's in the Standard gets updated to reflect that.

One option would be to define function calling in terms of
initialization but that may have unforeseen consequences. Something like

"Each argument shall have a type such that its value may be used as a
single expression initializer of its corresponding parameter."

and later:

"In preparing for the call to a function, the arguments are evaluated,
and each parameter is initialized using the value of the corresponding
argument."

At first glance, all the right wording is there, at least for scalar
types:

11 The initializer for a scalar shall be a single expression,
optionally enclosed in braces. The initial value of the object is
that of the expression (after conversion); the same type
constraints and conversions as for simple assignment apply, taking
the type of the scalar to be the unqualified version of its
declared type."

but then paragraph 13 surprised me a little:

13 The initializer for a structure or union object that has automatic
storage duration shall be either an initializer list as described
below, or a single expression that has compatible structure or
union type. In the latter case, the initial value of the object,
including unnamed members, is that of the expression.

This is from the "semantics" section, so for structure and union types
there is no constraint violation if the types are not compatible.
Whilst this is an odd anomaly of initialization, it's probably
unacceptable for function calls -- you really want to mandate a
diagnostic when the types don't match.

Also, on a side issue, the wording about disregarding the type's
qualifiers is also missing. Obviously

const struct s = (struct s){0};

is supposed to be permitted, so is "struct s" a "compatible structure or
union type"? Probably. It's not a compatible type, but the extra words
might be there to suggest that it's only the structural part that is to
be considered. I'd rather that were more explicit, though.
 
T

Tim Rentsch

Ben Bacarisse said:
Tim Rentsch said:
Ben Bacarisse <[email protected]> writes:
More curious to me is that gcc (in conforming mode) permits
this:

struct toto { const int i; const float f; const void *p; };

struct toto foo(int i, float f, void *p)
{
return (struct toto){i, f, p};
}

int bar(struct toto p) { return p.i; }

int main(void)
{
return bar(foo(1, 2, 0));
}

but 6.5.2.2 p2 says:

"Each argument shall have a type such that its value may be
assigned to an object with the unqualified version of the
type of its corresponding parameter."

The phrase "unqualified version of the type" can't surely be
intended to mean the recursive removal of all type qualifiers.
Elsewhere it simply means that the "top level" qualifiers are
removed. If that's the right interpretation, the call to bar
should be a constraint violation.

[speculation about why gcc acts as it does] Anyway, either
I'm misreading something or gcc has missed something.

[snip] this case also represents an oversight, [and it was
expected that such cases would be allowed.] All that remains
is making sure what's in the Standard gets updated to reflect
that.

One option would be to define function calling in terms of
initialization but that may have unforeseen consequences.
Something like

"Each argument shall have a type such that its value may be
used as a single expression initializer of its corresponding
parameter."

and later:

"In preparing for the call to a function, the arguments are
evaluated, and each parameter is initialized using the value
of the corresponding argument."

Yes, I think something along these lines is the right approach.
At first glance, all the right wording is there, at least for
scalar types:

11 The initializer for a scalar shall be a single expression,
optionally enclosed in braces. The initial value of the
object is that of the expression (after conversion); the
same type constraints and conversions as for simple
assignment apply, taking the type of the scalar to be the
unqualified version of its declared type."

but then paragraph 13 surprised me a little:

13 The initializer for a structure or union object that has
automatic storage duration shall be either an initializer
list as described below, or a single expression that has
compatible structure or union type. In the latter case,
the initial value of the object, including unnamed
members, is that of the expression.

This is from the "semantics" section, so for structure and union
types there is no constraint violation if the types are not
compatible. Whilst this is an odd anomaly of initialization,
it's probably unacceptable for function calls -- you really want
to mandate a diagnostic when the types don't match.

Surely this anomaly, as you call it, is just another oversight,
and the wording in p13 should be changed to be in line with
what everyone expects. That would eliminate any incongruity
with treating function arguments as initializing expressions,
and also remedy the point you identify next.
Also, on a side issue, the wording about disregarding the type's
qualifiers is also missing. Obviously

const struct s = (struct s){0};

is supposed to be permitted, so is "struct s" a "compatible
structure or union type"? Probably. It's not a compatible type,
but the extra words might be there to suggest that it's only the
structural part that is to be considered. I'd rather that were
more explicit, though.

One approach to the second problem would be to use type categories
instead of compatible type, eg,

13 The initializer for a structure or union object that has
automatic storage duration shall be either an initializer
list as described below, or a single expression having
the same type category as that of the structure or union
object. In the latter case, the initial value of the object,
including unnamed members, is that of the expression.

However, that wording by itself doesn't address the need for a
constraint violation for expressions not having the same type
category. (Note: surely it was intended that such cases be
constraint violations -- wasn't it?) Another idea is to change
p13 to have wording along the lines of what is used in p11.
Personally I kind of like the type category idea, but I think
using p11-like wording is more consistent, and so more likely
to be adopted if a change were proposed.
 

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

Forum statistics

Threads
474,077
Messages
2,570,566
Members
47,202
Latest member
misc.

Latest Threads

Top