简体   繁体   中英

Using nested class as parent class template parameter?

I have a nested class definition that I wanted to pass down as the parent class ( class containing the nested class, not the class that's being inherited ) template parameter. Since the template does not seem to be aware of the nested class's existance, I tried to pass it down as an incomplete type, only to read later that doing so is usually a bad idea and is only rarely permitted (such as in the case of shared_ptr ).

I know this can be very easily solved by simply declaring the nested class externally (which is what I do) but I am asking this because I want to learn if there is any way of achieving the same effect because I an fond of how they do not pollute the namespace how they are "associated" with the parent class definition without exposing anything.

Here's an example to perhaps make it more clear on what I am talking about:

#include <memory>

using namespace std;

template <class T> class shared_class {
protected:
  shared_ptr<T> d = make_shared<T>();

  T* container() { return d.get(); }
};

                                   // A is the "parent" class.
class A : public shared_class<C> { // Compiler error: C is undefined. 
                                   // Similarly, A::C will complain that no C is in A.
  class C {                        // Nested class
  public:
    int i = 0;
  };
};

Is there another way of doing this with templates in a way that the entire definition of the nested class is contained entirely within the parent class definition?

In short: no you can't do this. There are a few similar questions you can find on SO, but it all comes down to being able to forward-declare the nested class, which you can't do without a definition. See for example this question .

In other words, with great hand-waving, you might think you could do something like this:

class A;
class A::C; // Error: not real C++

class A : public shared_class<A::C> { // Error: incomplete base class
  class C {
  public:
    int i = 0;
  };
};

But you can't. You need to separate the declaration of C , and it needs a definition if it's going to be used as a base class. See also questions like this .

If it's just namespace encapsulation you most care about, you can always just use a dedicated namespace , that's what they're for.

There is no solution for your specific case with the requirements you gave, as far as I can tell.

Directly using C as template argument can't work in any way, simply because at that point the compiler hasn't seen yet that C is declared as a member class of A and because there is no way to declare the nested class before that point.


It doesn't work in your specific case, but with some limitations, you could add an indirection to the type of the shared_class template parameter via a traits template or directly via an alias declaration, which would allow you to delay the determination that T is supposed to be C until after the definition of A and keep C as nested class.

Unfortunately that doesn't work if you want to use the type T of shared_class in one of the declarations that would be instantiated with the class template specialization, here shared_ptr<T> d and T* container() . The latter could be saved by declaring the return type auto , but the former can't.

The idea would be to let shared_class use typename T::shared_class_type everywhere instead of T .

The class A would be defined as

class A : public shared_class<A> {
public:     
  class C {                        
  public:
    int i = 0;
  };
  using shared_class_type = C;
};

Alternatively you would define a template<typename T> struct shared_class_traits; , potentially with a default implementation containing using shared_class_type = typename T::shared_class_type; to be potentially specialized for A after its definition with a using shared_class_type = A::C; member, so that shared_class can use typename shared_class_traits<T>::shared_class_type everywhere. This is more flexible than "reserving" a specific member name of all classes using shared_class_type .

But as I explained above it will not work as long as shared_class uses shared_class_type at all in a context that is instantiated with the class template specialization. So inside a member function body or default member initializer would be fine, but in a data member or a member function type is not.

As the other answers pointed out, there is no direct solution for this problem, as there is no way to forward declare the class C.

To still be able to write A::C my solution would be:

namespace {
    class C_Anon{
    public:
        int i = 0;
    };
}

template <class T> class shared_class {
protected:
    shared_ptr<T> d = make_shared<T>();

    T* container() { return d.get(); }
};

// A is the "parent" class.
class A : public shared_class<C_Anon> {
    using C = C_Anon; //Allows for writing A::C
};

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM