5

I have following C++ code:

template <typename Type, typename T>
class zz
{
};

class foo
{
  template <typename T>
  using zz = ::zz<foo, T>;
  
  struct own_type : zz<double>
  {
    own_type():
      zz<foo, double>{} {} // ERROR: needs foo arg !!
  };
  
  template <typename T>
  struct zz_type_gen : zz<T>
  {
    zz_type_gen():
      zz<T>() {}
  };

  zz<int> _oi;
  zz_type_gen<char> _od;
};

Compiling with g++ 11, clang++ 12 and cl.exe with -std=c++20 works fine but if foo template argument is removed in line with // ERROR comment compilation fails. This seems to indicate that zz name is looked up first in global namespace inside nested class in case nested class (own_type) is not template. However zz name is looked up first in class foo in case nested class is template (zz_type_gen). I could not find clear explanation on how C++ names lookup works, but intuitively this seems inconsistent.

mkrieger1
  • 19,194
  • 5
  • 54
  • 65
Joseph
  • 53
  • 3
  • 1
    Pretty sure this has to do with the two pass compilation that templates go through. With `own_type`, since it is not a template the compiler knows that `zz` gives you the class `zz`, and because of [injected class names](https://stackoverflow.com/questions/25549652/why-is-there-an-injected-class-name), your zz alias gets hidden. With `zz_type_gen` , `zz` is a dependant name so the compiler doesn't know what the injected class name will be, so it allows `zz`. FWIW, renaming your alias to `zz_foo_t` or something like that would fix all of this. – NathanOliver Oct 08 '21 at 15:04
  • Thanks, yes having different names is the path I'll go, much less surprises, but wanted to get some explanation on apparent inconsistent behavior. – Joseph Oct 08 '21 at 15:14
  • 2
    You can also say `own_type() : zz{} {}` or `own_type() : foo::zz{} {}`. – n. m. could be an AI Oct 08 '21 at 15:24
  • @NathanOliver "your zz alias gets hidden" But then why isn't `::zz` hidden? Why, if you rename `foo::zz` alias to `foo::yy`, it doesn't get hidden the same way? It only happens when a nested alias aliases a global template of the same name. It does not happen when the nested alias aliases anything else (and a global template with the same name still exists but unrelated), and it doesn't happen when the nested template is not an alias. – n. m. could be an AI Oct 08 '21 at 15:36
  • 1
    @n.1.8e9-where's-my-sharem. Classes inject their name into the class scope. When you inherit from `zz`, it introduces that name into the scope of the derived class. That name being in scope stops lookup in the surrounding scope. – NathanOliver Oct 08 '21 at 15:38
  • @NathanOliver oh got it. Here's a more accessible (to me) explanation: `own_type : zz {...}` is exactly the same as `own_type : ::zz {...}`, and what is injected is `::zz`, not `foo::zz`. – n. m. could be an AI Oct 08 '21 at 15:43
  • @n.1.8e9-where's-my-sharem. Exactly. Because `own_type` is not a template, the compiler knows all of this, and the name hiding rule causes the error. With `zz_type_gen`, it's doesn't know what name `zz` will resolve to, so it lets it go. At least, that's what I suspect. – NathanOliver Oct 08 '21 at 15:46
  • @NathanOliver seems plausible – n. m. could be an AI Oct 08 '21 at 15:53

1 Answers1

2

To allow

namespace N {
  struct A {int f();};
}
struct B : N::A {
  int f() {return A::f()+1;}
};

without repeating the namespace qualification of A, and for the similar case of not repeating the template arguments of a class template, each class is considered to declare itself as a member (although this syntax is also used to declare or inherit constructors).

To prevent (specializations of) dependent base classes from shadowing names used in templated classes, unqualified name lookup is performed immediately in the template definition and ignores such bases entirely. This has the odd interaction with the injected-class-name model that even a name that would obviously refer to the base in each instantiation is sought outside the class instead. This does provide consistency with the case where the dependent base is more complicated than a template-id that names some specialization of a particular class template.

Therefore, own_type finds the injected-class-name of its base, which can be used either as a type-name (by omitting <foo, double>) or as the template-name that ::z is. zz_type_gen, however, finds foo::zz; it could use typename zz_type_gen::zz to find the injected-class-name instead of repeating the template argument list. (C++20 dispenses with the requirement for typename in some contexts but not others; a mem-initializer still requires it, but that was likely an oversight.)

Davis Herring
  • 36,443
  • 4
  • 48
  • 76