Search code examples
c++templatescompiler-errorsfriendclass-template

Nested not templated class inside a template class C++


here is my code

head.h


template <class T>
class Ext;

template <class T>
void fun(const Ext<T>&);

template <class T>
class Ext {
    friend void fun<T>(const Ext<T>&);

   private:
    class Inner;

    Inner* p;
};

template <class T>
void fun2(const typename Ext<T>::Inner&);

template <class T>
class Ext<T>::Inner {
    friend class Ext<T>;
    friend void fun<T>(const Ext<T>&);
    friend void fun2<T>(const typename Ext<T>::Inner&);

   private:
    Inner* q;
    int a;
    Inner() : a(1) {}
};

template <class T>
void fun2(const typename Ext<T>::Inner& i) {
    std::cout << i.a << std::endl;
}

template <class T>
void fun(const Ext<T>& e) {
    typename Ext<T>::Inner y;
    y.a = 3;
    fun2<T>(y);
}

main.cpp

int main() {
    Ext<int> x;
    fun(x);
}

g++ and (look below) clang give an

class Ext<int>::Inner’ is private within this context

So my question is : Is there a way to do this without making the inner class public? And why if i replace fun anf fun2 with an overload operator<< then it compiles even with inner class private (ONLY WITH CLANG)? like this

head.h

#include <iostream>

template <class T>
class Ext;

template <class T>
std::ostream& operator<<(std::ostream&, const Ext<T>&);

template <class T>
class Ext {
    friend std::ostream& operator<< <T>(std::ostream&, const Ext<T>&);

   private:
    class Inner;

    Inner* p;
};

template <class T>
std::ostream& operator<<(std::ostream&, const typename Ext<T>::Inner&);

template <class T>
class Ext<T>::Inner {
    friend class Ext<T>;
    friend std::ostream& operator<< <T>(std::ostream&, const typename Ext<T>::Inner&);

   private:
    Inner* q;
    int a;
    Inner() : a(1) {}
};

template <class T>
std::ostream& operator<<(std::ostream& os, const typename Ext<T>::Inner& n) {
    return os << n.a << " ";
}

template <class T>
std::ostream& operator<<(std::ostream& os, const Ext<T>& t) {
    os << "[ ";
    operator<< <T>(os, *(t.p));
    os << "] ";
    return os;
}

main.cpp

int main() {
    Ext<int> x;
    std::cout << x << std::endl;
}

i tried to compile with diffrent compilers but nothing works if i dont compile with clang and in that case its only compiles when the funcion is an overload of an operator


Solution

  • Is there a way to do this without making the inner class public?

    Yes, but there are several problems with the code all of which are explained and solved below.

    First, friendship is not associative in C++. This means that you need to add a friend template declaration for fun2 inside the class template Ext as shown in the code below.

    Second, the friend declaration that you currently have are non-template friend declarations. But since fun and fun2 are templates, you will need to change these to friend template declarations which is done by adding template<typename U> in the code shown below.

    The comments are also added in the below program to make it easier to spot the changes:

    #include <iostream>
    template <class T>
    class Ext;
    
    template <class T>
    void fun(const Ext<T>&);
    
    template <class T>
    class Ext {
        template<typename U> //added this template<typename U>
        friend void fun(const Ext<U>&); 
        //added this friend template declaration
        template <class U>
        friend void fun2(const typename Ext<U>::Inner& i);  
       private:
        class Inner;
    
        Inner* p;
    };
    
    template <class T>
    void fun2(const typename Ext<T>::Inner&);
      
    template <class T>
    class Ext<T>::Inner {
    
        template<typename U>  //added this template<typename U>
        friend void fun(const Ext<U>&);  
        
        template <class U>   //added this template<typename U>
        friend void fun2(const typename Ext<U>::Inner& i);         
     
       private:
        Inner* q; 
        int a;
        Inner() : a(1) {} 
    };
    
    template <class T>
    void fun2(const typename Ext<T>::Inner& i) {
        std::cout << i.a << std::endl; 
    } 
     
    template <class T>
    void fun(const Ext<T>& e) {
        typename Ext<T>::Inner y;  
        y.a = 3;
        fun2<T>(y);       
    }
    
    int main() {
        Ext<int> x;
        fun(x);
    } 
    

    Working demo

    Now the program works with all compilers.