mercredi 28 janvier 2015

Nested class specialization in terms of outer class template arguments

I am writing a Graph class and I want to provide multiple types of graphs with the same class. More specifically, I want to consider directed or undirected graphs, as well as the possibility for vertices (and edges) to have values.


Right now, my class looks something like that.



template<typename VertexValue = void, typename EdgeValue = void, bool directed = false>
class Graph {
public:
struct Vertex;
using Edge = Vertex::Edge;

Graph() = default;
//void addVertex(const VertexValue& vertex);

private:
std::vector<Vertex> m_vertices;
};


For convenience, I will skip anything related to edges implementation.


What I want to do now is to "specialize" the Vertex class to provide - or not - a value, and different methods depending on whether the graph is directed or not. I would like to be able to do something like the following (note : Vertex methods are only for illustrating purposes, directed and undirected versions of Vertex having different methods).



template<typename VertexValue = void, bool directed = false>
class Graph {
public:
struct Vertex;

private:
std::vector<Vertex> m_vertices;
};

// Directed, with value
template<typename VertexValue, bool directed>
struct Graph<VertexValue, directed>::Vertex {
using ValueType = VertexValue;

EdgeIterator inEdges();
EdgeIterator outEdges();

ValueType value;
};

// Directed, without value
template<bool directed>
struct Graph<void, directed>::Vertex {
using ValueType = void;

EdgeIterator inEdges();
EdgeIterator outEdges();
};

// Undirected, with value
template<typename VertexValue>
struct Graph<VertexValue, false>::Vertex {
using ValueType = VertexValue;

EdgeIterator edges();

ValueType value;
};

// Undirected, without value
template<>
struct Graph<void, false>::Vertex {
using ValueType = void;

EdgeIterator edges();
};


Unfortunately, I would have to specialize the whole Graph class in order to do that. So I tried something else : having the Vertex class being a template, and forwarding the outer template arguments.



template<typename VertexValue = void, bool directed = false>
class Graph {
public:
template<typename Value, bool, typename dummy = void>
struct Vertex;

private:
std::vector<Vertex<VertexValue, directed>> m_vertices;
};

// Directed, with value
template<typename VertexValue, bool directed>
template<typename Value, bool, typename dummy>
struct Graph<VertexValue, directed>::Vertex {
using ValueType = Value;

EdgeIterator inEdges();
EdgeIterator outEdges();

ValueType value;
};

// Directed, without value
template<typename VertexValue, bool directed>
template<bool directed_, typename dummy>
struct Graph<VertexValue, directed>::Vertex<void, directed_, dummy> {
using ValueType = void;

EdgeIterator inEdges();
EdgeIterator outEdges();
};

// Undirected, with value
template<typename VertexValue, bool directed>
template<typename Value, typename dummy>
struct Graph<VertexValue, directed>::Vertex<Value, false, dummy> {
using ValueType = Value;

EdgeIterator edges();

ValueType value;
};

// Undirected, without value
template<typename VertexValue, bool directed>
template<typename dummy>
struct Graph<VertexValue, directed>::Vertex<void, false, dummy> {
using ValueType = void;

EdgeIterator edges();
};


This would work ; however, I do not like the idea of forwarding the template arguments, and it also introduces a burden, the dummy template parameter. I have also thought about defining the Vertex class outside of the Graph class, like so :



template<typename ValueType, bool directed>
class GraphVertex {
EdgeIterator inEdges();
EdgeIterator outEdges();

ValueType value;
};

// Specializations...

template<typename Vertex = GraphVertex<void, false>>
class Graph {
public:
// ...
private:
std::vector<Vertex> m_vertices;
};


...or even like that :



template<typename ValueType, bool directed>
class GraphVertex {
EdgeIterator inEdges();
EdgeIterator outEdges();

ValueType value;
};

// Specializations...

template<typename VertexValue = void, bool directed = false>
class Graph {
public:
// ...
private:
std::vector<GraphVertex<VertexValue, directed>> m_vertices;
};


...but I prefer the idea of nested class, where the Vertex class really is a property of the Graph class. Plus, even if this solution, out of the three, would still have my preference, I do not find any other name for "GraphVertex". And it bothers me.


In the end, here is my question. Is there any other way to do what I want (namely, provide different Vertex classes - or features - depending on the Graph template arguments) ? If so, how to and what would be the advantages ; if not, what should I prefer and why ?


Aucun commentaire:

Enregistrer un commentaire