13

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.

Xeo
  • 129,499
  • 52
  • 291
  • 397
Anne van Rossum
  • 3,091
  • 1
  • 35
  • 39

4 Answers4

8

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.

Community
  • 1
  • 1
Xeo
  • 129,499
  • 52
  • 291
  • 397
  • +1 I suppose under '//optional' you wanted `passkey(passkey&&) = default;` though, not delete, since you take the passkey by value. – sehe Oct 24 '12 at 21:36
  • @sehe: Nah, I meant `= delete`, and would take it by reference-to-const. Having it both uncopyable and unmovable makes it really hard to share the key, which one may want. The only way I can think of would be using the key as a class member and returning a reference to it from a member, or having it as a static variable and returning a reference to that. – Xeo Oct 24 '12 at 21:38
  • +1 This really solves the full problem and concisely explains what is actually befriended. Trying to figure out how to keep the "T" @pubby is indeed not trivial. :-) – Anne van Rossum Oct 25 '12 at 10:11
6

You can template the friend statement:

template<typename U, ClassImplType  V>
friend class graph_foo;

I'm trying to figure out how to keep the T - I'll update if I find out.

Pubby
  • 51,882
  • 13
  • 139
  • 180
  • `error: partial specialization ‘graph’ declared ‘friend’`, g++ 4.4.3. – Robᵩ Oct 24 '12 at 18:33
  • @Robᵩ Yeah, that's because of the nested templates. I fixed it using `using` in C++11, but I think there is a better way. Actually I didn't fix it, hehe. – Pubby Oct 24 '12 at 18:36
  • "`template friend class graph_foo;`" isn't it? – Mooing Duck Oct 24 '12 at 18:45
  • @MooingDuck No, that's seen as a specialization. – Pubby Oct 24 '12 at 18:46
  • @Pubby mmm. seeing as this doesn't seem actually work, are you going to edit/delete this? – sehe Oct 24 '12 at 20:22
  • @sehe AFAIK it works, it is just not specific. I'll leave it as the "short but not perfect" answer. – Pubby Oct 24 '12 at 20:26
  • +1 Let's hope befriending all possible specializations won't be a problem for now. In many practical situations this is probably the way to go. Just for the reader, there is a difference between: `template friend class graph_foo;` and `template friend class graph_foo;` – Anne van Rossum Oct 25 '12 at 10:16
  • @ondervloei: For the code you posted, no, there isn't, except that the latter produces an error since `vertex`'s template parameter is named `T`. – Xeo Oct 25 '12 at 13:05
  • @Xeo, exactly, you're right, and that one letter difference might be missed by an otherwise diligent reader. :-) – Anne van Rossum Nov 13 '12 at 00:17
  • @ondervloei: Oh, that's what you were going for... carry on, then. :) – Xeo Nov 13 '12 at 00:19
3

I thought of the following way to 'fix it' by using recursive inheritance.

Inline comments explain what's happening:

#include <type_traits>
#include <string>
#include <iostream>
#include <typeinfo>

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

template <typename T, ClassImplType impl_type = CIT_CHECK>
class graph;

// Vertex class
namespace impl
{
    template <typename, ClassImplType, typename enabler = void> struct vertex_impl;

    ///////////////////////////////////////////////////////////////
    // actual implementation (stop condition of recursion)
    static const ClassImplType CIT_ENDMARKER = (ClassImplType) -1;

    template <typename T> struct vertex_impl<T, CIT_ENDMARKER> 
    {
        protected: // make it protected rather than private
            int secret() const { return 42; }
    };

    ///////////////////////////////////////////////////////////////
    // recursion, just to mark friends
    template <typename T, ClassImplType impl_type>
        struct vertex_impl<T, impl_type, typename std::enable_if<CIT_ENDMARKER != impl_type>::type>
            : public vertex_impl<T, ClassImplType(impl_type - 1)>
        {
             friend class ::graph<T, impl_type>;
        };
}

///////////////////////////////////////////////////////////////
// Public typedef
#if 1
    template <typename T> struct vertex : impl::vertex_impl<T, CIT_NOF_TYPES> { };
#else // or c++11
    template <typename T> using vertex = impl::vertex_impl<T, CIT_NOF_TYPES>;
#endif

template <typename T, ClassImplType impl_type>
class graph
{
    public:
        static void TestFriendOf(const vertex<T>& byref)
        {
            std::cout << byref.secret() << std::endl;
        }
};

int main(int argc, const char *argv[])
{
    vertex<int> t;

    graph<int, CIT_CHECK>     :: TestFriendOf(t);
    graph<int, CIT_FAST>      :: TestFriendOf(t);
    graph<int, CIT_GPU>       :: TestFriendOf(t);
    graph<int, CIT_SSE>       :: TestFriendOf(t);
    graph<int, CIT_NOF_TYPES> :: TestFriendOf(t);
}

This works on gcc and clang.

See it live on http://liveworkspace.org/code/f03c0e25a566a4ca44500f4aaecdd354

PS. This doesn't exactly solve any verbosity issues at first sight, but you could make the solution more generic, and inherit from a different 'base-case' instead of the hard-coded one, in which case the boileplate could potentially be compensated for (in case you have many classes that need to befriend families of graph types)

sehe
  • 374,641
  • 47
  • 450
  • 633
2

Class cannot become friend of partial specialization according to this "non-bug" explanation: http://gcc.gnu.org/bugzilla/show_bug.cgi?id=5094

 What clenches it is that 14.5.3, p9
 explicitly prohibits friend declarations of partial specializations:

   -9- Friend declarations shall not declare partial specializations.
       [Example:
           template<class T> class A { };
           class X {
               template<class T> friend class A<T*>; // error
           };
       容nd example]

But I come to the solution, which does not look perfect, neither it is easy to use. The idea is to create intermediate friend inner class, just to forward "friendship" to outer class. The disadvantage (or advantage?) is that one has to wrap all function/member variables which shall be available to outer friend:

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

template <typename T>
class vertex;

// Graph class has default template argument CIT_CHECK
template <typename T, ClassImplType impl_type = CIT_CHECK>
class graph {
    typedef typename vertex<T>::template graph_friend<impl_type> graph_friend;
public:
  graph(vertex<T>& a) { graph_friend::foo(a); } // here call private method    
  //...
};

// Vertex class
template <typename T>
class vertex {
  //...
  int foo() {}
public:
  template <ClassImplType impl_type>
  class graph_friend {
     static int foo(vertex& v) { return v.foo(); }
     friend class graph<T,impl_type>;
  };

};
int main() {
    vertex<int> a;
    graph<int,CIT_SSE> b(a);

}
PiotrNycz
  • 23,099
  • 7
  • 66
  • 112