1

With Visual Studio 2019 (v 16.7.3), I have found that a template class can access the private members of it non-template base class. Is this an expected behavior or is it a compiler bug?

When the derived class is not a template class, the private member of the base class is inaccessible as expected:

class A
{
};

class Base
{
public:
    Base() : m_pA(new A()) {}
private:
    A* m_pA;
};

class Derived :
    public Base
{
public:
    Derived() : Base() {}
    A* get_a() { return m_pA; }  // 'Base::m_pA': cannot access private member declared in class 'Base' 
};

int main()
{
    Derived d;
}

== Build: 0 succeeded, 1 failed, 0 up-to-date, 0 skipped ==

However, if I make Derived a template class, it compiles without complaint:

template<class T>
class Derived :
    public Base
{
public:
    Derived() : Base() {}
    A* get_a() { return m_pA; }
};

int main()
{
    Derived<int> d;
}

== Build: 1 succeeded, 0 failed, 0 up-to-date, 0 skipped ==
Jan Hettich
  • 9,146
  • 4
  • 35
  • 34
  • Can you call get_a on the derived class – Jens Munk Apr 02 '21 at 02:48
  • Clang successes to find the error [Demo](https://godbolt.org/z/4efMGEnzE) (even if not required). – Jarod42 Apr 02 '21 at 07:50
  • Does this answer your question? [encapsulation and friend classes in C++](https://stackoverflow.com/questions/32202742/encapsulation-and-friend-classes-in-c) – outis May 09 '22 at 22:46

1 Answers1

5

The posted code does not call Derived<int>::get_a() so that template member function does not get instantiated (and there is no "access" of the private parent member to speak of).

Adding the following get_a() call causes the same error as in the non-template case.

{
    Derived<int> d;
    d.get_a();  // error: 'A* Base::m_pA' is private within this context
}

[ EDIT ]   The implicit instantiation of template member functions (only) when used is ruled in the C++ standard under temp.inst/4:

Unless a member of a templated class is a declared specialization, the specialization of the member is implicitly instantiated when the specialization is referenced in a context that requires the member definition to exist or if the existence of the definition of the member affects the semantics of the program;

An explicit definition of the template specialization (though not just a declaration) will cause full instantiation of all members, resulting in the same error.

extern template class Derived<int>; // explicit declaration - ok

template class Derived<int>;        // explicit definition - error: 'A* Base::m_pA' is private within this context

[ EDIT #2 ]   The argument was made in the comments (thanks @Jarod42) that the given code falls into the "ill-formed, no diagnostic required" category, meaning the compiler is allowed (though not required) to reject it on grounds that no possible (complete) instantiation of the class template Derived<T> can exist because of the private member access in get_a() even if the member function is never used.

This interpretation is based on the following section of temp.res.general/6.1:

The program is ill-formed, no diagnostic required, if:

  • no valid specialization can be generated for a template or a substatement of a constexpr if statement within a template and the template is not instantiated, or [...]
  • If "valid specialization can be generated" is taken to mean a complete explicit specialization of the entire template class, then the given code snippet is indeed "ill-formed NDR".

  • If, however, "valid specialization" is understood to cover implicit specializations of the template class with (only) the required members as defined in temp.inst/4 quoted earlier, then the given code snippet does not fall under "ill-formed NDR" because get_a() is never instantiated (per temp.inst/11: "an implementation shall not implicitly instantiate a function template, a variable template, a member template, a non-virtual member function, a member class or static data member of a templated class, or a substatement of a constexpr if statement, unless such instantiation is required").

The linked demo shows that gcc takes the latter interpretation, while clang the former.

In either case, the answer stands that if the template is not rejected upfront as "ill-formed NDR" because of the no-valid-specializations-can-exist clause, then it will compile and work fine as long as get_a() does not get instantiated, either implicitly by direct reference, or by an explicit specialization of the template class.

dxiv
  • 16,984
  • 2
  • 27
  • 49
  • 1
    Right. It's important to remember what a template is - it's not code, it's *instructions* for generating the code once you have the template types filled in. If there's no call to the function, no code gets generated and so no error gets generated. – Mark Ransom Apr 02 '21 at 02:57
  • 1
    Notice that the code is ill-formed NDR (as no valid instantiations might exist). clang successes to diagnose it :-) [Demo](https://godbolt.org/z/4efMGEnzE) – Jarod42 Apr 02 '21 at 07:52
  • @Jarod42 That's interesting. A warning maybe, but I don't see why a hard error would be warranted there. The member function in question never gets instantiated in that snippet. – dxiv Apr 02 '21 at 08:04
  • Hard error as there are no `U` such as `Derived::get_a` is well formed. same as `static_assert(false);`. `m_pA` is not even dependent, so the check is relatively easy here. – Jarod42 Apr 02 '21 at 08:12
  • @Jarod42 Maybe I am misreading it, but AFAICT there should be no check in this case because there is no instantiation. From [temp.inst/11](https://eel.is/c++draft/temp.inst#11): "*An implementation* ***shall not implicitly instantiate*** *a function template, a variable template, a member template,* ***a non-virtual member function***, *a member class or static data member of a templated class, or a substatement of a constexpr if statement,* ***unless such instantiation is required***". – dxiv Apr 02 '21 at 15:09
  • [temp#res.general-6.1](https://eel.is/c++draft/temp#res.general-6.1) (and 6.4). – Jarod42 Apr 02 '21 at 15:14
  • @Jarod42 Thanks, I see. Though that says "*the program is ill-formed, no diagnostic required, if:* ***no valid specialization*** *can be generated* ***for a template*** *or a substatement of a constexpr if statement within a template and the template is not instantiated*". However, the previous quote says that implicitly instantiating a ***template*** does not mean automatically instantiating ***non-virtual member functions***. The example at [temp.inst/6](https://eel.is/c++draft/temp.inst#6) appears to support that ("*nothing ... requires Z​::​g() ... to be implicitly instantiated*"). – dxiv Apr 02 '21 at 15:39
  • Related SO question: [Member function instantiation](https://stackoverflow.com/questions/26123254/member-function-instantiation). – dxiv Apr 02 '21 at 15:40
  • I didn't say that `Derived::get_a` is/should be instantiated. `dummy::new_t()` is a valid possible instantiation, so `new_t` is not ill-formed. – Jarod42 Apr 02 '21 at 16:38
  • @Jarod42 Guess it depends on how you read and piece together "*valid specialization*" against "*implicit instantiation*", though I'll leave that to the finer language lawyers. I added a 2nd edit to the question to (hopefully) summarize this. FWIW even clang doesn't appear to be entirely consistent about it, for example I'd have expected it to reject [this](https://godbolt.org/z/nonh9bMxh) for the same reason, but it does not. – dxiv Apr 02 '21 at 18:05
  • NDR, It misses that opportunity. NDR applies mostly to hard/impossible to diagnose issue. I would says that this kind of diagnostic is just a bonus, as second pass will found the issue anyway, and we want to write code which is used. – Jarod42 Apr 06 '21 at 13:15