Search code examples
c++templatesc++11template-meta-programmingoverload-resolution

Alternatives for std::enable_if and explicit overloading for template template parameters


Consider the following setup:

template< typename Held >
class Node{
  //...
};

template< typename Held >
class vNode{
  //...
};

template <typename... Graphs>
class Branch{
  //...
};

template <typename...> class Graph; // undefined

template< 
  typename    node_t
> class Graph< node_t >{  //specialization for an ending node
  //...
};

template< 
  typename    node_t,
  typename... Graphs
> class Graph< node_t, Branch< Graphs...> >{  //specialization for a mid-graph node
  //...
};

template<
  template <typename> class node_t,
  typename Held
> void f( Graph< Node<Held> > ) {
  //stuff A on a node
}

template<
  template <typename> class node_t,
  typename Held
> void f( Graph< const Node<Held> > ) {
  //stuff A on a const node
}

template<
  template <typename> class node_t,
  typename Held
> void f( Graph< vNode<Held> > ) {
  //stuff B on a virtual node
}

template<
  template <typename> class node_t,
  typename Held
> void f( Graph< const vNode<Held> > ) {
   //stuff B on a virtual const node
}

template<
  template <typename> class node_t,
  typename Held,
  typename... Graphs
> void f( Graph< Node<Held>, Branch<Graphs...>> ) {
  //stuff C on a node with a branch
}

template<
  template <typename> class node_t,
  typename Held,
  typename... Graphs
> void f( Graph< const Node<Held>, Branch<Graphs...> > ) {
  //stuff C on a const node with a branch
}

template<
  template <typename> class node_t,
  typename Held,
  typename... Graphs
> void f( Graph< vNode<Held>, Branch<Graphs...> > ) {
  //stuff D on a virtual node with a branch
}

template<
  template <typename> class node_t,
  typename Held,
  typename... Graphs
> void f( Graph< const vNode<Held>, Branch<Graphs...> > ) {
   //stuff D on a virtual const node with a branch
}

In other words - I'm creating a type that represents a graph. Nodes can be normal, or virtual, const and non-const. A Graph can contain a single node, or a node and a branch of graphs.

When I create a function f I want it to be const-neutral (do the same stuff on a const and non-const version of a node in a graph, but different on branched and unbranched graphs). Do I have to:

  1. Duplicate the code?
  2. Use std::enable_if hack?

    1. Duplication of code duplicates bugs, so it is not optimal.
    2. std::enable_if produces bad error messages in my case.

Is there a smarter solution to the problem that will make f accept const and non-const nodes?


Solution

  • Instead of using a template template parameter and have a gazillion overloads, just use a type template parameter:

    template<class T> void f( Graph<T> ) { /*...*/ }
    

    T will be deduced to be Node<Foo>, vNode<Foo> or const Node<Foo> etc. as appropriate. If Node vs vNode matters, you can always extract the type of the node with a simple trait class. Similarly, you can use static_assert together with a trait class to ensure that T is a specialization of Node or vNode.