cplusplus / CWG

Core Working Group
23 stars 7 forks source link

CWG2862 [temp.pre] Is it a class template with a declarator, or is it a variable template that defines a class? #506

Open Eisenwave opened 4 months ago

Eisenwave commented 4 months ago

Full name of submitter (unless configured in github; will be published with the issue):

Reference (section label): [temp.pre] p5

Issue description

It is unclear whether the following construct is permitted (rejected by all compilers):

template <int>
struct S {} v;

This could be

Suggestion resolution

If this construct is intended to be ill-formed, modify [temp.pre] p3 as follows:

 A template-declaration is a declaration.
 A declaration introduced by a template declaration of a variable is a variable template
-.
+, unless the decl-specifier-seq of the declaration contains a class-specifier.
 A variable template at class scope is a static data member template.

If this construct is intended to be well-formed, modify [temp.pre] p5 as follows:

 In a template-declaration, explicit specialization, or explicit instantiation
 the init-declarator-list in the declaration shall contain at most one declarator.
-When such a declaration is used to declare a class template, no declarator is permitted.
zygoloid commented 4 months ago

I was expecting that we could say this is editorial, on the basis that the declaration in question would be both a class template and a variable template, so [temp.pre]/5 would apply. But it seems we have a larger issue here: we never actually define the term "class template" at all. The definition is not trivial:

// class template
template<typename> class X;
// not class template
template<typename> class X y;
Eisenwave commented 4 months ago

But it seems we have a larger issue here: we never actually define the term "class template" at all.

Yeah, I also find the "editorial definition" for class templates to be in need of work. I'm not sure if fixing that would be too much for a core issue though; perhaps a paper is required, especially if the fix is meant to apply to all sorts of templates which may not be properly defined right now.

The definition is not trivial:

It may still be relatively easy if you leave all these cases to the logic which decides whether something is a class or variable. I.e. define a class template to be a template-declaration where the declaration declares a class.

If so, the following would be a variable template:

template <int>
struct S {} y;

I believe the rule of thumb should be

It's the same kind of declaration as if you removed the template-head.

killerbee13 commented 4 months ago

I believe that the declaration would be simultaneously a class declaration and a variable declaration, which would make it ([temp.pre]p5 notwithstanding, and ignoring the fact that "class template" is not defined) simultaneously a class template and variable template. That way, [temp.pre]p5 is correct as written.

I can't find a definition for "class declaration" either, but a note in [class.name] does distinguish a class declaration and an elaborated type specifier, seemingly even if that elaborated type specifier also declares the class name. I believe that that note is incorrectly worded, as otherwise

template <typename>
class S;

would (presumably) not be a class template.

zygoloid commented 4 months ago

I believe the rule of thumb should be

It's the same kind of declaration as if you removed the template-head.

I don't think that's quite what we want. Specifically:

// This is a class declaration.
struct A *x;
// This is not a class template declaration.
template<typename T> struct A *y;

template<typename T> struct B;

// This is not a class declaration (it's not even valid).
struct B<int*>;
// This is a class template declaration.
template<typename U> struct B<U*>;

That is, for elaborated-type-specifiers we want both cases in [dcl.type.elab]/2 and not the case in [dcl.type.elab]/3, whereas "declares a class" is the first case in [dcl.type.elab]/2 plus the case in [dcl.type.elab]/3.

Perhaps we should say something like (in [temp.pre]/3):

A class template is a declaration introduced by a template declaration whose declaration is a simple-declaration that either contains a class-specifier in its decl-specifier-seq or comprises solely of an elaborated-type-specifier.

but maybe we should make sure all the different kinds of template are properly defined. Then perhaps [temp.pre]/5:

In a template-declaration, explicit specialization, or explicit instantiation the init-declarator-list in the declaration shall contain at most one declarator. When such a declaration is used to declare a class template, no declarator is permitted.

... can be replaced with "A template-declaration shall declare exactly one template." or similar :)

frederick-vs-ja commented 4 months ago

437 is related. Perhaps we should resolve it together.

jensmaurer commented 4 months ago

CWG2862

zygoloid commented 4 months ago

CWG2862

Hm. I think my suggestion doesn't quite work:

template<typename T> struct A {
  struct B;
};
// This is not a class template, but I think the new definition says that it is.
template<typename T> struct A<T>::B {};

(The existing definition of "variable template" is wrong in the same way.)

Maybe something more like this:

If the declaration in a template-declaration introduces an entity with a dependent nested-name-specifier that is equivalent to the injected-class-name of a class template or class template partial specialization T, and the template-head of the template-declaration is equivalent to that of T, the template-declaration declares a member of T, and the nested-name-specifier is treated as a non-dependent reference to T for the purpose of further interpreting the declaration. Otherwise:

  • A class template is introduced by a template-declaration whose declaration is a simple-declaration that either contains a class-specifier in its decl-specifier-seq or comprises solely of an elaborated-type-specifier.
  • A function template is introduced by a template-declaration whose declaration declares a function.
  • An alias template is introduced by a template-declaration whose declaration is an alias-declaration.
  • A variable template is introduced by a template-declaration whose declaration declares a variable. [ Example:
    template<typename T> struct A {
    template<typename U> struct B;
    template<typename U> struct B<U*> {
    template<typename V> void f();
    };
    };
    template<typename T> // #1
    template<typename U> // #2
    template<typename V> // #3
    void A<T>::B<U*>::f() {}
  • The template-declaration #\1 declares a member of the class template A, because A<T> is equivalent to the injected-class-name of A.
  • The template-declaration #\2 declares a member of the class template partial specialization A<T>::B<U*>, because A<T>::B<U*> is equivalent to the injected-class-name of the partial specialization when A<T> is treated as a non-dependent reference to the primary template A.
  • The template-declaration #\3 declares a function template that is a member of the class template partial specialization. -- end example]

... though I'm not sure about the exact phrasing here, particularly the "treated as non-dependent" part. I think we need soemthing to say "you actually need to do name lookup inside this nested-name-specifier even though it's a template with dependent arguments".

jensmaurer commented 4 months ago

Looks like an improvement; I've integrated your suggestion into the core issue, with two minor tweaks:

zygoloid commented 4 months ago
  • It's not /any/ dependent nested-name-specifier that gets the special treatment (in particular, the return type doesn't get a special treatment for this paragraph), it's just the one in the declarator-id.

I think we need to cover more than that -- we should also apply this to the nested-name-specifier in an elaborated-type-specifier, class-head-name, or enum-head-name, as well as a nested-name-specifiers that is a prefix of another nested-name-specifier with this treatment.

zygoloid commented 4 months ago

Also:

A template-declaration shall declare exactly one template or member of a template.

jensmaurer commented 4 months ago

Hm... The special nested-name-specifier rules you gave are exactly those of a "declarative nested-name-specifier" in [expr.prim.id.qual] p2 --- except for the elaborated-type-specifier case. And [dcl.type.elab] doesn't seem to mention a case where a qualified elaborated-type-specifier would be able to declare a template. (in particular, you can't declare members out-of-class unless that declaration is a definition, in which case we're in the class-head-name situation, not the elaborated-type-specifier case.)

jensmaurer commented 4 months ago

CWG2862 is updated.

zygoloid commented 4 months ago

[dcl.type.elab] doesn't seem to mention a case where a qualified elaborated-type-specifier would be able to declare a template.

Interesting. We almost hit this situation for partial specializations of member templates:

template<typename T> struct A {
  template<typename U> struct B;
};
// Error, can't declare a specialization of a member of an unspecialized template.
template<typename T> template<typename U>
struct A<T>::B<U*>;

... but not quite. We do hit this for template friends:

struct Z {
  template<typename T> friend struct A<T>::B;
};

But [temp.friend]/5 has custom rules for that, so perhaps we don't need to address it here?

jensmaurer commented 4 months ago

Yeah, the temp.friend rules seem to be very specific and add additional permission via "may".

t3nsor commented 3 months ago

I feel that there should be an example that illustrates the implication of the "treated as a non-dependent reference to C for the purpose of further interpreting the declaration" part of this wording (in particular, why the word "non-dependent" needs to be there). Otherwise I can't tell what it's supposed to be doing.