1

While trying to get some old software to compile with clang, I encountered some code similar to the following:

class OuterClass {
private:
  template <class T>
  class InnerClass {};
};

template <class T>
class OtherClass {};


template <class T>
class OtherClass<OuterClass::InnerClass<T>> {};

My understanding of this code is that the template class OtherClass is specialized for instantiations of the private inner template class OuterClass::InnerClass.

g++-10 (version 10.2.0) seems to compile this code without any errors or warnings, but clang++-10 (version 10.0.0) complains that "'InnerClass' is a private member of 'OuterClass'" at the template specialization.

Of course, I could make InnerClass public, but, since InnerClass is private in the original code, I think this solution would not be ideal. Is there a way I could allow InnerClass to be used in the specialization of OtherClass only (perhaps with some clever use of friend)?


I thought maybe I could do something like this, (which is similar to the approach taken in this answer to a similar question but I get an error stating that "partial specialization cannot be declared as a friend."

  template <typename T>
  friend class OtherClass<InnerClass<T>>;
fakedad
  • 1,292
  • 1
  • 10
  • 21
  • 1
    Possible [duplicate](https://stackoverflow.com/questions/38761076/template-specialization-for-private-types). – 1201ProgramAlarm Jan 21 '21 at 00:22
  • @1201ProgramAlarm That question is similar, and I hadn't seen it before. I'm having difficulty, though, adapting the other question's answer to solve my problem. I think the difference is that `InnerClass` is itself a template class, whereas `Secret` in the question linked is a plain (i.e., non-template) struct. – fakedad Jan 21 '21 at 01:11

1 Answers1

1

The posted code appears to compile under gcc only because the template is never actually instantiated (and no code is generated), but fails if attempting to instantiate it.

For OtherClass<InnerClass<T>> instantiations to work, the inner type needs to be made accessible to the template classes. One way to do it is by declaring those as a public type in OuterClass.

#include <iostream>

template <class T>
class OtherClass;

class OuterClass {
private:
  template <class T>
  class InnerClass { };

public:
  template <class T>
  using OtherInner = OtherClass<InnerClass<T>>;
};

template <class T>
class OtherClass {
public:
  OtherClass() { std::cout << "other<T>" << std::endl; }
};

template <class T>
class OtherClass<OuterClass::InnerClass<T>> {
public:
  OtherClass() { std::cout << "other<inner>" << std::endl; }
};

int main() {
  OtherClass<char> a;                        // output:  other<T>
  OuterClass::OtherInner<int> b;             // output:  other<inner>
// OtherClass<OuterClass::InnerClass<A>> c;  // error:   InnerClass is private
}

The above assumes that OuterClass knows about the OtherClass templates that it wants to expose InnerClass to. Probably a reasonable assumption in the context, otherwise InnerClass could simply be made public and accessible to all other classes/templates indiscriminately.

dxiv
  • 16,984
  • 2
  • 27
  • 49
  • 4
    It's actually meant to be valid C++20 http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0692r1.html . If the type is made accessible some other way (say a method that returns it), then one could name those specializations via `OtherClass`. – StoryTeller - Unslander Monica Jan 21 '21 at 07:44
  • @StoryTeller-UnslanderMonica Good read, thanks for the reference. Though I must be misunderstanding something about that comment under Option A "*this would just work*". AFAICT it does indeed work in `gcc` to declare the specialization, but [fails](https://godbolt.org/z/8ef5jn) if trying to instantiate it. – dxiv Jan 21 '21 at 08:07
  • 1
    It doesn't reach the instantiation in that example. The access itself to the type is invalid outside the definition of the specialization. If one could name the type some other way outside the class (or in a member function of the outer class) the specialization should be instantiated. – StoryTeller - Unslander Monica Jan 21 '21 at 08:46
  • 1
    @dxiv: Similarly, with [access-to-private-members-thats-easy](http://bloglitb.blogspot.com/2010/07/access-to-private-members-thats-easy.html) [Demo](https://godbolt.org/z/nK6cn4), explicit instantiation can be done, so with OP's code [Demo](https://godbolt.org/z/7rrTYq) (clang reject it though). Don't know if we can steal (template) types as we do for members though with that method. – Jarod42 Jan 21 '21 at 10:24
  • @Jarod42 Thanks for those points, I didn't realize this was all so close to the cutting edge. One difficulty in OP's case is indeed the template type, which is why the technique in the other linked post fails. Meanwhile, the way I proposed above looks somewhat more conservative, and in a way truer to the intent of letting the class control which of its private members to expose to who else. – dxiv Jan 21 '21 at 16:56