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.