0

I'm trying to build a 'non-centralized' graph structure where nodes and edges cross-point themselves. Quite similar to this: Circular template reference structure.

Although, I'm trying to support that nodes can hold several edges types of varying 'arity'. And that, reciprocally, edge's many nodes types.

¿Is there a way to achieve this with generic programming / metaprogramming without relaying on inheritance?

The following approach that I tried highlights the problem:

#include <iostream>
#include <tuple>
#include <list>

using namespace std;

template<class ...EdgeTypes>
class node
{
  public:
    std::tuple<std::list<EdgeTypes*>...> edges_;

    template<size_t I>
    using container_value_type_i = typename std::tuple_element<I, decltype(edges_) >::type::value_type;

    template< std::size_t I>
    void addEdge(const container_value_type_i<I>& e)
    { std::get<I>(edges_).push_back(e); }
};

template<template<class...> class ...V>
class edge
{

  public:

    std::tuple<V<edge>*...> vertices_;
    //           ^^^^
    // its forcing that all nodes have the same number of 'edges types' (one)
    // and that they are of the same 'arity' as edge object

    template<size_t I>
    using vertex_type_i = typename std::tuple_element<I, decltype(vertices_) >::type;

    template< std::size_t I>
    void addNode(const vertex_type_i<I>& e)
    { std::get<I>(vertices_) = e; }

};

int main()
{
  edge<node> unary_edge;
  edge<node, node> binary_edge;

  node<edge<node>> unary_node_of_unary_edges;

  unary_node_of_unary_edges.addEdge<0>(&unary_edge);
  unary_edge.addNode<0>(&unary_node_of_unary_edges);

  node<edge<node, node>> unary_node_of_binary_edges;

  unary_node_of_binary_edges.addEdge<0>(&binary_edge);

  // This won't compile as edge type of node's edges are not the same as unary_edge
  //unary_edge.addNode<0>(unary_node_of_binary_edges);

  node<edge<node>, edge<node, node>> binary_node_of_unary_edges_and_binary_edges;

  binary_node_of_unary_edges_and_binary_edges.addEdge<0>(&unary_edge);
  binary_node_of_unary_edges_and_binary_edges.addEdge<1>(&binary_edge);

  // This won't compile as nodes's edges are not all the same type
  //unary_edge.addNode<0>(&binary_node_of_unary_edges_and_binary_edges);
  return 0;
}
Tomerikoo
  • 18,379
  • 16
  • 47
  • 61
CaTo
  • 69
  • 1
  • 8

1 Answers1

1

You could encode the entire airity structure of the graph in the type system, but I doubt that is useful.

Type erasure is always an option. Ignoring that, you could use templates for both edge type and node type, and max airity.

template<template<class...>class Z>
struct ztemplate{
  template<class...Ts>
  using z=Z<Ts...>;
};
template<class zE, class Indexes>
class Node;
using zNode=ztemplate<Node>;
template<class zN, class Indexes, class Count>
class Edge;
using zEdge=ztemplate<Edge>;

template<class zE, std::size_t...Is>
class Node<zE,std::index_sequence<Is...>>{
  std::tuple< std::list< zE::template z<zNode, std::index_sequence<Is...>, std::integral_constant<std::size_t,Is>>... > edges;
};
template<class zN, std::size_t...Is, std::size_t A>
class Edge<zN,std::index_sequence<Is...>, std::integral_constant<std::size_t,A>>{
  std::array< zN::template z<zEdge, std::index_sequence<Is...>>*, A> nodes;
};

or somesuch.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • what a beast, took me a bit to get it. Although, 'nodes arities' of an edge must be all the same (uses array). Is there a way that an edge accepts nodes of different arity? Maybe template wrapping both zN and Is together? – CaTo Mar 29 '18 at 22:31
  • 1
    @cato the problem is that you rapidly approach "need entire structure of graph to determine types of each node" without some type erasure lubrication. By using a max airity on the nullable lists we provide that. There are other spots where you could apply type erasure instead. – Yakk - Adam Nevraumont Mar 29 '18 at 23:20