This is an unofficial snapshot of the ISO/IEC JTC1 SC22 WG21 Core Issues List revision 115e. See http://www.open-std.org/jtc1/sc22/wg21/ for the official list.

2024-11-11


2920. The template keyword for base classes

Section: 13.3  [temp.names]     Status: open     Submitter: Corentin Jabot     Date: 2024-07-15

Consider:

  template<int T>
  struct BaseT {
    void Foo() {}
  };

  template<int T>
  struct DerivedT : BaseT<T> {
    void Inner() {
      this->template /* unnecessary keyword before CWG1835 */ BaseT<T>::Foo();
    }
  };

The resolution of issue 1835 in P1787 now requires the template keyword in this situation. This breaks code in several large projects.

The reason to require the template keyword is that the injected-class-name, like any other member, is inherited from the base class, but the base class is dependent, and thus name lookup does not consider base class members when parsing the template definition. For example:

  namespace N
  {
    template<int I>
    struct A { };

    template<int I>
    struct B
    {
      using C = A<I>;

      template<int J>
      using D = A<J>;
    };
  }

  // impossible to determine the injected-class-name of the base class in the definition context since 'N::B' could be specialized.
  // it might not be the injected-class-name of a template, and it might not even be 'C'/'D'!

  template<int I>
  struct X : N::B<I>::C { }; 

  template<int I>
  struct Y : N::B<I>::template D<I> { };

Even if we specified how to find the injected-class-name of a dependent base class for a subset of cases, we would reintroduce the problem that the resolution to issue 1835 solves:

  template<int I>
  struct A
  {
    int A;
  };

  bool f();

  template<int I>
  struct B : A<I>
  {
    bool g()
    {
      return this->A < I; // do we interpret '<' as the start of a template-argument-list here?
    }

    bool h()
    {
      return this->A < I > ::f(); // how about here?
    }
  };

Using the unqualified lookup results from the template definition context for disambiguation when the object expression is dependent is also wrong since they are discarded if we find a member in the template instantiation context. Consider:

  namespace N
  {
    template<int I>
    struct A
    {
      int B;
    };
  }

  template<int I>
  using B = N::A<I>;

  template<int I>
  struct C : B<I>
  {
    bool f()
    {
      return this->B < I;
    }
  };

Since the lookup context of B in this->B is dependent, we cannot determine whether it would be invalid as a complete class member access expression. We also cannot parse past the '<' to determine whether B is the terminal name of a member-qualified nested-name-specifier, so we do not know if unqualified name lookup should happen. Issue 1835 resolves this by treating this->B as a complete class member access expression. Using the results of unqualified name lookup to determine whether B is the name of a template is incorrect because it may refer to a non-template member when C is instantiated.

CWG 2024-08-16

The resolution for issue 1835 in P1787 has addressed real concerns. CWG recognizes that real-world code now no longer compiles, although the fix for the affected source code is trivial. A limited exception to support some of the existing code might be feasible. CWG solicits a paper with specification and analysis.