Search code examples
c++templatesargumentsfriend

C++ How to specify all friends of a templated class with a default argument?


To define a friend of a templated class with a default argument, do you need to specify all friends as in the code below (which works)?

// Different class implementations
enum ClassImplType { CIT_CHECK, CIT_FAST, CIT_GPU, CIT_SSE, CIT_NOF_TYPES } ;

// Graph class has default template argument CIT_CHECK
template <typename T, ClassImplType impl_type = CIT_CHECK>
class graph {
  //...
};

// Vertex class
template <typename T>
class vertex {
  //...
  friend class graph<T, CIT_CHECK>;
  friend class graph<T, CIT_FAST>;
  friend class graph<T, CIT_GPU>;
  friend class graph<T, CIT_SSE>;
};

I can imagine that there is a shorter way to denote that the friend is defined for all possible ClassImplType enum values. Something like friend class graph<T, ClassImplType>, but the latter doesn't work of course.

Apologies if the terminology I use is incorrect.


Solution

  • I can imagine that there is a shorter way to denote that the friend is defined for all possible ClassImplType enum values.

    Sadly, there really isn't. You might try with

    template<ClassImplType I> friend class graph<T, I>;
    

    but the standard simply forbids one to befriend partial specializations:

    §14.5.4 [temp.friend] p8

    Friend declarations shall not declare partial specializations. [ Example:

    template<class T> class A { };
    class X {
      template<class T> friend class A<T*>; // error
    };
    

    —end example ]

    You can only either befriend them all:

    template<class U, ClassImplType I>
    friend class graph;
    

    Or a specific one:

    friend class graph<T /*, optional-second-arg*/>;
    

    I can't see how befriending all possible specializations might cause a problem here, to be honest, but let's assume it does. One workaround I know would be using the passkey pattern, though we'll use a slightly cut-down version (we can't use the allow mechanism here, since it doesn't work well for allowing access to all specializations of a template):

    template<class T>
    class passkey{    
      passkey(){}
      friend T;
    
      // optional
      //passkey(passkey const&) = delete;
      //passkey(passkey&&) = delete;
    };
    
    // Different class implementations
    enum ClassImplType { CIT_CHECK, CIT_FAST, CIT_GPU, CIT_SSE, CIT_NOF_TYPES } ;
    
    template<class> struct vertex;
    
    // Graph class has default template argument CIT_CHECK
    template <typename T, ClassImplType impl_type = CIT_CHECK>
    class graph {
    public:
      void call_f(vertex<T>& v){ v.f(passkey<graph>()); }
      //...
    };
    
    // Vertex class
    template <typename T>
    class vertex {
      //...
    public:
      template<ClassImplType I>
      void f(passkey<graph<T,I>>){}
    };
    

    Live example with tests.

    You'll note that you need to make all functionality that graph needs to access public, but that's not a problem thanks to the passkeys, which can only ever be created by the specified graph specializations.

    You can also go farther and create a proxy class which can be used to access the vertex functionality (only graph changes):

    // Graph class has default template argument CIT_CHECK
    template <typename T, ClassImplType impl_type = CIT_CHECK>
    class graph{
      typedef passkey<graph> key;
      // proxy for succinct multiple operations
      struct vertex_access{
        vertex_access(vertex<T>& v, key k)
          : _v(v), _key(k){}
    
        void f(){ _v.f(_key); }
    
      private:
        vertex<T>& _v;
        key _key;
      };
    
    public:
      void call_f(vertex<T>& v){
        vertex_access va(v, key());
        va.f(); va.f(); va.f();
        // or
        v.f(key());
      }
      //...
    };
    

    Live example.