cplusplus / draft

C++ standards drafts
http://www.open-std.org/jtc1/sc22/wg21/
5.69k stars 749 forks source link

[dcl.array] p8 A contradictory example and the rule should be improved #5464

Open xmh0511 opened 2 years ago

xmh0511 commented 2 years ago

[dcl.array] p8 says

Furthermore, if there is a reachable declaration of the entity that inhabits the same scope in which the bound was specified, an omitted array bound is taken to be the same as in that earlier declaration, and similarly for the definition of a static data member of a class.

Consider the example

struct S {
  static int y[10];
};
int S::y[];             // OK, bound is 10

It's not true. Since the declaration outside the class definition does not inhabit the same scope as the initial declaration does. Since [basic.scope.scope] p2 says

Unless otherwise specified:

  • [...]
  • A declaration inhabits the immediate scope at its locus ([basic.scope.pdecl]).

The locus of the second declaration is immediately after the complete declarator, the scope it inhabits is the namespace scope. Moreover, [dcl.meaning.general] p3.1 strongly implies this point

If the id-expression in the declarator-id of the declarator is a qualified-id Q, let S be its lookup context ([basic.lookup.qual]); the declaration shall inhabit a namespace scope.

The comment is just wrong. If the comments are the intent. We might say

Furthermore, if there is a reachable declaration D to which the name is bound in the same scope in which the name introduced by another declaration D0 that omits the array bound is bound, where D and D0 declares the same entity and D specifies the array bound, the omitted array bound is taken to be the same as D.

extern int x[10];
void f() {
extern int x[];  // the name is bound in this scope rather than global namespace
int i = sizeof(x);    // error: incomplete object type
}

The earlier declaration implies that the declaration with the specified bound should be declared before another declaration that omits the bound, which rejects this valid example

extern int arr[];
int arr[10];
int i = sizeof(arr);

@jensmaurer @opensdh

jensmaurer commented 2 years ago

I take the "similarly for the definition of a static data member of a class." that your case of a static data member is one that doesn't fit the "inhabits the same scope" rule. We could flesh that out a little by saying "An omitted array bound in the definition of a static data member of a class is taken to be the same as the array bound specified in the declaration of the static data member in the /class-specifier/."

To your second point, I'm not reading this from the normative text you quoted. "earlier declaration" applies only if that has a specified bound. In your case, the earlier declaration doesn't have a bound, so the rule doesn't apply.

opensdh commented 2 years ago

I think there could still be a problem here:

namespace N {extern int x[1];}
int N::x[];

The "inhabits the same scope" ("in the same scope" in C++20) is meant to deal with the ugly cases of block-scope extern declarations.

xmh0511 commented 2 years ago

In your case, the earlier declaration doesn't have a bound, so the rule doesn't apply.

[basic.lookup.general] p1 says

Only after name lookup, function overload resolution (if applicable) and access checking have succeeded are the semantic properties introduced by the declarations used in further processing.

extern int arr[];  // #1 earlier declaration
int arr[10];  // #2 later declaration
int i = sizeof(arr);  // #3

All declarations can be found at #3, which declaration determines the size property at #3? In normal cases, two declarations that declare the same entity should have the same type per [basic.link] p11. However, this is an exception for declaring array. Although, [basic.types.general] p6 says

The declared type of an array object can be an array of unknown bound and therefore be incomplete at one point in a translation unit and complete later on; the array types at those two points (“array of unknown bound of T” and “array of N T”) are different types.

Consider a more confused example

extern char buffer[];
char buffer[128];
decltype(buffer) arr;

which declaration's type is used in the declaration of arr?

xmh0511 commented 2 years ago

The "inhabits the same scope" ("in the same scope" in C++20) is meant to deal with the ugly cases of block-scope extern declarations.

@opensdh So, that's why I want to use "Name is bound in the scope" to try to clarify the rule. [dcl.meaning.general] p3.5 has specified that the name introduced by a block variable declared with extern is bound in the block scope the declaration inhabits, whereas the innermost enclosing namespace declaration(if any) binds its name in the namespace scope. Altough, it is not clear to me that whether a declaration whose declarator-id is an qualified-id introduces a name or not. [dcl.meaning.general] p3.4 grants that such a declaration's target scope is the same as that of the initial declaration. [basic.scope.scope] p2 says

Unless otherwise specified:

  • [...]
  • Any names (re)introduced by a declaration are bound to it in its target scope.

The only doubt for the suggestion is whether the qualified-id as a declarator-id introduces a name(qualified name)?

jensmaurer commented 2 years ago

I don't like "bound in a scope" for this purposes. Name binding sometimes does not occur (e.g. for friends), and we're not really talking about name lookup here, but about declaration matching and inheriting properties from earlier declarations.

xmh0511 commented 2 years ago

@jensmaurer About the opinion in this https://github.com/cplusplus/draft/issues/5464#issuecomment-1124427614. What do you think about that? which declaration of the entity determines the properties when the name is used.

xmh0511 commented 2 years ago

I don't like "bound in a scope" for this purposes. Name binding sometimes does not occur (e.g. for friends), and we're not really talking about name lookup here

"name bound in a scope" is a trick that wants to refer to [dcl.meaning.general] p3.5 to check whether the declaration that omits the array bound inhabits a block scope. I think [dcl.meaning] does not talk about names but declarations. Except for this expectation, all other declarations whose declarator-ids are qualified-ids can inherit the array bound from the initial declaration even though they do not inhabit the same scope. Maybe, the direct way is:

Furthermore, if there is a reachable declaration of the entity that supplies the array bound, an omitted array bound of a declaration of the entity is taken to be the same as in that declaration. If the declarator-ids of the two declarations are both unqualified-ids, they shall inhabit the same scope.

jensmaurer commented 2 years ago

Oh, we agree that "inhabit the same scope" is not the right phrasing to use; neither for namespace members nor for class members. I just disagree that "bound in a scope" is necessarily the right tool to fix the phrasing.

I think that the properties of an entity are determined by the union of all declarations that are reachable. "inline" might be in a similar category.

xmh0511 commented 2 years ago

I think that the properties of an entity are determined by the union of all declarations that are reachable. "inline" might be in a similar category.

This point is not very clear in the current draft. We might change the last sentence of [basic.lookup.general] p1 to make that meaning clear.

Only after name lookup, function overload resolution (if applicable) and access checking have succeeded is the union of these semantic properties introduced by the declarations used in further processing.