Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Nested-name-specifier is not mentioned for member definition outside its target scope #4592

Open
xmh0511 opened this issue Apr 23, 2021 · 18 comments
Labels
cwg Issue must be reviewed by CWG.

Comments

@xmh0511
Copy link
Contributor

xmh0511 commented Apr 23, 2021

In c++20 standard, For the member functions or static member data which are these members that can be defined outside its enclosing class, it has a normative rule for how to define them outside the enclosing class, they are
For member functions
class#mfct-3

If the definition of a member function is lexically outside its class definition, the member function name shall be qualified by its class name using the ​::​ operator.

For static data member
class#static.data-3

In the definition at namespace scope, the name of the static data member shall be qualified by its class name using the ​::​operator.

As well as the definition of members of a namespace
namespace.memdef#2

Members of a named namespace can also be defined outside that namespace by explicit qualification ([namespace.qual]) of the name being defined, provided that the entity being defined was already declared in the namespace and the definition appears after the point of declaration in a namespace that encloses the declaration's namespace.

I tried to find out the alternative normative rule in the current draft that can replace these omission rules, however, I don't find them out. Is it considered a bit radical modification that excises these rules?

@jensmaurer
Copy link
Member

These permissions are, in fact, just redeclarations that happen to be definitions. The generic redeclaration rules handle these cases, I believe.

@xmh0511
Copy link
Contributor Author

xmh0511 commented Apr 23, 2021

These permissions are, in fact, just redeclarations that happen to be definitions. The generic redeclaration rules handle these cases, I believe.

I doubt that these rules in the above are intended to be covered by [dcl.meaning]. However, it's not explicitly. I have to try to read [dcl.meaning] more carefully to check whether the meaning is readable.

@languagelawyer
Copy link
Contributor

languagelawyer commented Apr 24, 2021

I think what are you looking for is [expr.prim.id.qual]/2 and /3. (Yes, it is an unexpected place for declaration rules).

@xmh0511
Copy link
Contributor Author

xmh0511 commented Apr 24, 2021

I think what are you looking for is [expr.prim.id.qual]/2 and /3. (Yes, it is an unexpected place for declaration rules).

[expr.prim.id.qual]/2 and /3 are basiclly introducing the concept about declarative nested-name-specifier, it is useful for determining where can the declaration inhabit. It didn't state that we should redeclare the member by using nested-name-specifier.

@jensmaurer
Copy link
Member

By what other means would you possibly refer to that member? If you omit the declarative nested-name-specifier, you're not redeclaring the entity you introduced inside the class or namespace definition.

See [dcl.meaning.general] p3.4, and [basic.scope.scope] p3 for "correspond".

@xmh0511
Copy link
Contributor Author

xmh0511 commented Apr 24, 2021

By what other means would you possibly refer to that member? If you omit the declarative nested-name-specifier, you're not redeclaring the entity you introduced inside the class or namespace definition.

See [dcl.meaning.general] p3.4, and [basic.scope.scope] p3 for "correspond".

After reading [dcl.meaning] carefully, It is not necessary to be used to interpret the following rules, I think [basic.link#8] is sufficient to interpret that

struct A{
  void fun();  //#1
}; 
void fun(){}  //#2

#1 and #2 do not correspond and do not have the same target scope, hence they do not declare the same entity.
However, consider an example in [class#pre-example-1]

namespace N {
  template<class>
  struct A {
    struct B;
  };
}
using N::A;
template<> struct A<void> {};   // error: A not nominable in ​::​

the declarator A<void> satisfies [dcl.meaning#general]-3.2, hence S is the global namespace here. p3.3 says it does not bind a name. Only the first sentence of p3.4 can apply to this case since A<void> is not a qualified name. The first sentence only says that the terminal name A is not looked up. I don't know what's the actual intent for that it states that A is not looked up. I suppose that it intends to say that using N::A does not contribute to the terminal name A of A<void>(i.e, A here does not refer to the primary template defined in N). p3.5 does not apply here. So, [dcl.meaning] does not have more explanation for this case to interpret why the case is an error.
The remaining rule for restricting how an explicit specialization declaration should be is defined as

An explicit specialization does not introduce a name ([basic.scope.scope]). A declaration of a function template, class template, or variable template being explicitly specialized shall be reachable from the declaration of the explicit specialization.

As per module.reach#3, the primary template is indeed reachable at the point where the explicit specialization defined. In my humble opinion, it still no rule that explicitly interprets why the case is an error. Moreover, the note in the example seems not reasonable, since A<void> where the terminal A is not a qualified name, there's no rule that requires that it shall satisfy what said in "If it is a qualified name, the declarator shall correspond to one or more declarations nominable in S;".

@xmh0511
Copy link
Contributor Author

xmh0511 commented Apr 25, 2021

After comparing P1787r5 and P1787r6, there's a quite different for [class#pre-3] between them. In P1787r5, it says

If a class-head-name contains a nested-name-specifier, the class-specifier shall not inhabit a class scope; let E be the class, class template, or namespace to which the nested-name-specifier refers, Otherwise, if its class-name is a simple-template-id, let E be the entity associated with its immediate scope. In either case, the class-specifier shall (ignoring any template argument list in the class-name) correspond to one or more declarations nominable in E ([basic.scope.scope]); they shall all have the same target scope, and the target scope of the class-specifier is that scope.

While in P1787r6(i.e, be adopted by the current draft)

If a class-head-name contains a nested-name-specifier, the class-specifier shall not inhabit a class scope. If its class-name is an identifier, the class-specifier shall correspond to one or more declarations nominable in the class, class template, or namespace to which the nested-name-specifier refers; they shall all have the same target scope, and the target scope of the class-specifier is that scope.

It appears to me that the rule in P1787r5 is more clear except that it is unclear whether an explicit specialization declaration corresponds to its primary template declaration since an explicit specialization does not introduce a name, hence it does not satisfy the requirement for that two declarations are corresponding. The remaining sentence can indicate the reason why template<> struct A<void> {}; is an error. Since the named E is the global namespace, as per [basic.scope#scope-5], the primary template A does not nominable in the global namespace, now the note is reasonable. However, the current [class#pre-3] is difficult to interpret this point.

Is that consider how to interpret the example below the rule when changing [class#pre-3] in P1787r5 to the current content? Please take a look at this issue @opensdh @jensmaurer @languagelawyer

@opensdh
Copy link
Contributor

opensdh commented Apr 30, 2021

the declarator A<void>

A class-head-name is not a declarator. I think you're right, however, that [class.pre]/3 is inconsistent with its example. From my best reading of the minutes of the discussion on the subject, the example is just wrong (despite being added at the same time as the aforementioned change to the paragraph): both class definitions should just be // OK, which is arguably editorial.

Meanwhile, that change removed the rule that specified the target scope for a template specialization/instantiation. That doesn't matter for the correspondence check, since that was removed too, but it is necessary to back up the note in [temp.pre]/7. I'll add that to my list of fixups for P1787.

@xmh0511
Copy link
Contributor Author

xmh0511 commented May 6, 2021

the declarator A<void>

A class-head-name is not a declarator. I think you're right, however, that [class.pre]/3 is inconsistent with its example. From my best reading of the minutes of the discussion on the subject, the example is just wrong (despite being added at the same time as the aforementioned change to the paragraph): both class definitions should just be // OK, which is arguably editorial.

Meanwhile, that change removed the rule that specified the target scope for a template specialization/instantiation. That doesn't matter for the correspondence check, since that was removed too, but it is necessary to back up the note in [temp.pre]/7. I'll add that to my list of fixups for P1787.

Appreciate your clarification. That means that the definition of a partial/explicit specialization for a class template does not have to inhabit a scope where the primary class template is nominable; The partial/explicit specialization can also be defined at a scope in which the lookup will find the class template name, as the issue [defines a member of a namespace without using nested-name-specifier] pointed out before P1787 goes into the standard, no matter what kind of declaration would make the name be found(in the formal example, that's using-declaration while the issue on SO, that's using namespace directive).

@jensmaurer
Copy link
Member

#4624 fixes the [class.pre] p3 example to say "ok". @opensdh, is this what you want?

(I understand there's also a core issue lurking here for [temp.pre] p7, which you have on your list of fixups.)

@jensmaurer jensmaurer added the cwg Issue must be reviewed by CWG. label Jun 3, 2021
@xmh0511
Copy link
Contributor Author

xmh0511 commented Jun 4, 2021

@jensmaurer @opensdh Although the example is clarified to be "ok", but it looks like there's no relationship between [class.pre#3] and that example since there's no powerful evidence in [class.pre#3] states this example. As @opensdh has pointed out, A<void> is not declarator-id instead it's class-name, hence the whole [dcl.meaning] subclause is not suitable to clarify this example. Although, [basic.lookup#general-1] says that:

The name lookup rules apply uniformly to all names (including typedef-names ([dcl.typedef]), namespace-names ([basic.namespace]), and class-names ([class.name])) wherever the grammar allows such names in the context discussed by a particular rule.

As we see in [dcl.meaning], it explicitly points out which cases are necessary to be looked up and which are not, such as

If it declares a class member, the terminal name of the declarator-id is not looked up;

A similar way for wording the rule of [namespace.def]

If the lookup finds a namespace-definition for a namespace N...

So, could we extend the [class.pre#3] by adding the relevant rule for that example?

Otherwise, if the class-head-name is a simple-template-id, the terminal name of the simple-template-id is (unqualified name) looked up; they shall all have the same target scope, and the target scope of the class-specifier is that scope.

I think the "otherwise part" would cover this example.

@opensdh
Copy link
Contributor

opensdh commented Jun 11, 2021

#4624 fixes the [class.pre] p3 example to say "ok". @opensdh, is this what you want?

It's certainly correct. @xmh0511 makes a valid point that the connection to the normative text is somewhat lacking; it might be good to extend the example slightly to illustrate the differences like so:

namespace N {
  struct X;
  template<class>
  struct A {
    struct B;
  };
}
namespace O {using N::X;}
using N::A;
struct O::X {};                         // error: X not nominable in O
template<class T> struct A<T>::B {};    // OK
template<> struct ::A<void> {};         // OK

So, could we extend the [class.pre#3] by adding the relevant rule for that example?

Either that or just specify the (reuse of the) target scope for all template specializations/instantiations. We don't need an explicit appeal to name lookup, because [basic.lookup.unqual]/4 and [basic.lookup.qual.general]/3 say that it happens everywhere by default. (It's true that [basic.lookup.general]/1 is a bit optimistic with the word "uniformly": I guess it means all kinds of names.)

@xmh0511
Copy link
Contributor Author

xmh0511 commented Jun 12, 2021

just specify the (reuse of the) target scope for all template specializations/instantiations.

IMHO, I think target scope might not suitable here. For instance,

namespace N {
  template<class>
  struct A {
  };
}
using N::A;
template<> struct A<void> {};   //#1

The target scope of the declaration at #1 is the global namespace's scope due to the following rule

Unless otherwise specified:

  • [...]
  • A declaration inhabits the immediate scope at its locus ([basic.scope.pdecl]).
  • A declaration's target scope is the scope it inhabits.

There's no otherwise place that states what is the target scope of an instantiation/explicit specialization. Does that mean the explicit instantiation/specialization and its primary template can have a different target scope? Although, this point(whether they shall have the same target scope) is not specified in the standard.

@opensdh
Copy link
Contributor

opensdh commented Jun 12, 2021

That's the problem I've already identified, yes.

@xmh0511
Copy link
Contributor Author

xmh0511 commented Jun 12, 2021

That's the problem I've already identified, yes.

That will no something worry about if the issue has recorded in your fixed list. Please word the rule for stating what the target scope of an (explicit/implicit)instantiation / explicit specialization would be. For implicit instantiation, the rule temp.inst#12

Implicitly instantiated class, function, and variable template specializations are placed in the namespace where the template is defined. Implicitly instantiated specializations for members of a class template are placed in the namespace where the enclosing class template is defined. Implicitly instantiated member templates are placed in the namespace where the enclosing class or class template is defined.

has been removed from the current standard. I think it might also be necessary to be clarified.

@opensdh
Copy link
Contributor

opensdh commented Jun 12, 2021

"placed in the namespace" never meant anything anyway, as far as I'm aware.

@xmh0511
Copy link
Contributor Author

xmh0511 commented Jun 12, 2021

"placed in the namespace" never meant anything anyway, as far as I'm aware.

Yes. I mean we could use these terminologies introduced by P1787 to give meaning to what the target scope of an (explicit/implicit)instantiation / explicit specialization would be.

@xmh0511
Copy link
Contributor Author

xmh0511 commented Jan 6, 2022

Record:

namespace N {
  template<class>
  struct A {
  };
}  
using namespace N;
template<> struct A<void>{}; // ok #1
// template struct A<int>;  // #2

The explicit instantiation at #2 is not accepted by the implementations that accept #1. Don't know what the difference here, [temp. explicit] also did not give any restriction. @opensdh @jensmaurer

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
cwg Issue must be reviewed by CWG.
Projects
None yet
Development

No branches or pull requests

4 participants