Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 631
Anne van Rossum Avatar asked Oct 24 '12 18:10

Anne van Rossum


People also ask

CAN default arguments be used with the template class?

Default parameters for templates in C++: Like function default arguments, templates can also have default arguments.

How will you restrict the template for a specific datatype?

There are ways to restrict the types you can use inside a template you write by using specific typedefs inside your template. This will ensure that the compilation of the template specialisation for a type that does not include that particular typedef will fail, so you can selectively support/not support certain types.

What is template argument list?

A template argument for a template template parameter is the name of a class template. When the compiler tries to find a template to match the template template argument, it only considers primary class templates. (A primary template is the template that is being specialized.)

What is the difference between typename and class in template?

There is no semantic difference between class and typename in a template-parameter. typename however is possible in another context when using templates - to hint at the compiler that you are referring to a dependent type. §14.6.


1 Answers

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.

like image 85
Xeo Avatar answered Nov 05 '22 22:11

Xeo