Class templates that are both base classes and directly usable - c++

I have two classes representing a graph:
class Node {
public:
void AppendSource(Edge &Edge) { m_Sources.append(&Edge); }
void AppendSink(Edge &Edge) { m_Sinks.append(&Edge); }
QList<Edge *> &Sources() { return m_Sources; }
QList<Edge *> &Sinks() { return m_Sinks; }
QList<Edge *> const &Sources() const { return m_Sources; }
QList<Edge *> const &Sinks() const { return m_Sinks; }
protected:
QList<Edge *> m_Sources;
QList<Edge *> m_Sinks;
}; // Node
class Edge {
public:
Edge(Node &Source, Node &Sink) : m_pSource(&Source), m_pSink(&Sink) {}
Node const &Source() const { return *m_pSource; }
Node const &Sink() const { return *m_pSink; }
Node &Source() { return *m_pSource; }
Node &Sink() { return *m_pSink; }
void SetSource(Node &Source) { m_pSource = &Source; }
void SetSink(Node &Sink) { m_pSink = &Sink; }
protected:
Node *m_pSource;
Node *m_pSink;
}; // Edge
It should be possible to inherit from these classes, in order to add functionality for specific types of graphs. Therefore, the classes should be template classes:
template <class EDGE_TYPE>
class Node {
public:
void AppendSource(EDGE_TYPE &Edge) { m_Sources.append(&Edge); }
void AppendSink(EDGE_TYPE &Edge) { m_Sinks.append(&Edge); }
QList<EDGE_TYPE *> &Sources() { return m_Sources; }
QList<EDGE_TYPE *> &Sinks() { return m_Sinks; }
QList<EDGE_TYPE *> const &Sources() const { return m_Sources; }
QList<EDGE_TYPE *> const &Sinks() const { return m_Sinks; }
protected:
QList<EDGE_TYPE *> m_Sources;
QList<EDGE_TYPE *> m_Sinks;
}; // Node
template <class NODE_TYPE>
class Edge {
public:
Edge(NODE_TYPE &Source, NODE_TYPE &Sink) : m_pSource(&Source), m_pSink(&Sink) {}
NODE_TYPE const &Source() const { return *m_pSource; }
NODE_TYPE const &Sink() const { return *m_pSink; }
NODE_TYPE &Source() { return *m_pSource; }
NODE_TYPE &Sink() { return *m_pSink; }
void SetSource(NODE_TYPE &Source) { m_pSource = &Source; }
void SetSink(NODE_TYPE &Sink) { m_pSink = &Sink; }
protected:
NODE_TYPE *m_pSource;
NODE_TYPE *m_pSink;
}; // Edge
But now it seems no longer possible to use the classes without extending them! A few attempts with the obvious corresponding errors:
new Node(); // 'Node': use of class template requires template argument list
new Node<>(); // 'Node': too few template arguments
new Node<Edge>(); // 'Edge': unspecialized class template can't be used as a template argument for template parameter 'EDGE_TYPE', expected a real type
new Node<Edge<>>(); // 'Edge': too few template arguments
new Node<Edge<Node>>(); // 'Node': unspecialized class template can't be used as a template argument for template parameter 'NODE_TYPE', expected a real type
new Node<Edge<Node<>>>(); // 'Node': too few template arguments
I was hoping to solve this by introducing defaults for the template arguments. A few attempt with their corresponding errors:
template <class EDGE_TYPE = Edge>
class Node { ... }
template <class NODE_TYPE = Node>
class Edge { ... }
new Node<>(); // 'Edge': unspecialized class template can't be used as a template argument for template parameter 'EDGE_TYPE', expected a real type
template <class EDGE_TYPE = Edge<>>
class Node { ... }
template <class NODE_TYPE = Node<>>
class Edge { ... }
new Node<>(); // recursive type or function dependency context too complex
template <class EDGE_TYPE = Edge<Node<EDGE_TYPE>>>
class Node { ... }
template <class NODE_TYPE = Node<Edge<NODE_TYPE>>>
class Edge { ... }
new Node<>(); // 'EDGE_TYPE': undeclared identifier
How can I make Node and Edge both directly usable and extensible though inheritance?

How can I make Node and Edge both directly usable and extensible
though inheritance?
I'll focus on that requirement in bold.
Though you can have the definition of Edge and Node depend on each other, there's no way to make recurring declaration of Edge and Node, because such declaration would yield infinite template recursion:
Node<> = Node<Edge<>> = Node<Edge<Node<>>> = Node<EdgeNode<Edge<>>>> ...
So, if you want Edge<> and Node<> to be directly usable (i.e. instantiateable without making dummy derived classes), then you should break that recursion. For example, by making both Edge and Node depend on some third traits class:
// Forward declaration.
struct DefaultGraphTypes;
template <typename GraphTypes = DefaultGraphTypes>
struct Node;
template <typename GraphTypes = DefaultGraphTypes>
struct Edge;
// Traits class.
template <typename NodeT, typename EdgeT>
struct GraphTypes
{
// Could change this to 'using' in modern C++
typedef NodeT FinalNodeType;
typedef EdgeT FinalEdgeType;
// typedef MayBeSomeOtherParameters ...
};
struct DefaultGraphTypes
: public GraphTypes<Node<DefaultGraphTypes>, Edge<DefaultGraphTypes>>
{
};
// Implementation of graph classes.
template <typename GraphTypes>
struct Node
{
typedef typename GraphTypes::FinalNodeType FinalNodeType;
typedef typename GraphTypes::FinalEdgeType FinalEdgeType;
// ... Your implementation
};
template <typename GraphTypes>
struct Edge
{
typedef typename GraphTypes::FinalNodeType FinalNodeType;
typedef typename GraphTypes::FinalEdgeType FinalEdgeType;
// ... Your implementation
};
// User-derived types.
struct MyNode;
struct MyEdge;
struct MyNode
: public Node<GraphTypes<MyNode, MyEdge>>
{
// Something specific
};
struct MyEdge
: public Edge<GraphTypes<MyNode, MyEdge>>
{
// Something specific
};
// Test
int main()
{
Node<> n1;
Edge<> e1;
MyNode n2;
MyEdge e2;
return 0;
}

IMHO, this can be achieved by inserting forward declarations at the right place. I took parts of OP sample code and completed it to a compilable sample:
#include <iostream>
#include <vector>
#define QList std::vector // Sorry, no Qt at hand in coliru
template <class EDGE_TYPE>
class Node {
public:
void AppendSource(EDGE_TYPE &Edge) { m_Sources.append(&Edge); }
void AppendSink(EDGE_TYPE &Edge) { m_Sinks.append(&Edge); }
QList<EDGE_TYPE *> &Sources() { return m_Sources; }
QList<EDGE_TYPE *> &Sinks() { return m_Sinks; }
QList<EDGE_TYPE *> const &Sources() const { return m_Sources; }
QList<EDGE_TYPE *> const &Sinks() const { return m_Sinks; }
protected:
QList<EDGE_TYPE *> m_Sources;
QList<EDGE_TYPE *> m_Sinks;
}; // Node
template <class NODE_TYPE>
class Edge {
public:
Edge(NODE_TYPE &Source, NODE_TYPE &Sink) : m_pSource(&Source), m_pSink(&Sink) {}
NODE_TYPE const &Source() const { return *m_pSource; }
NODE_TYPE const &Sink() const { return *m_pSink; }
NODE_TYPE &Source() { return *m_pSource; }
NODE_TYPE &Sink() { return *m_pSink; }
void SetSource(NODE_TYPE &Source) { m_pSource = &Source; }
void SetSink(NODE_TYPE &Sink) { m_pSink = &Sink; }
protected:
NODE_TYPE *m_pSource;
NODE_TYPE *m_pSink;
}; // Edge
// forward declarations:
struct WNode;
struct WEdge;
// declaration of derived types
struct WNode: public Node<WEdge>
{
int weight;
};
struct WEdge: public Edge<WNode>
{
int weight;
WEdge(WNode &src, WNode &snk): Edge(src, snk) { }
};
// check whether it compiles
int main()
{
WNode node1, node2;
WEdge edge12(node1, node2);
// done
return 0;
}
Live Demo on coliru
That's the actual trick:
OP carefully used in template class Node and template class Edge only references and pointers to the resp. opposite type. Hence, an incomplete type is fully sufficient to be used as template argument in both cases. These incomplete types are provided by the forward declarations:
// forward declarations:
struct WNode;
struct WEdge;
Afterwards, the classes WNode and WEdge can be derived from Node<WEdge> and Edge<WNode>.

template template parameter might help:
template <typename TEdge>
class Node {
public:
using EDGE_TYPE = TEdge;
void AppendSource(EDGE_TYPE &Edge) { m_Sources.append(&Edge); }
void AppendSink(EDGE_TYPE &Edge) { m_Sinks.append(&Edge); }
QList<EDGE_TYPE *> &Sources() { return m_Sources; }
QList<EDGE_TYPE *> &Sinks() { return m_Sinks; }
QList<EDGE_TYPE *> const &Sources() const { return m_Sources; }
QList<EDGE_TYPE *> const &Sinks() const { return m_Sinks; }
protected:
QList<EDGE_TYPE *> m_Sources;
QList<EDGE_TYPE *> m_Sinks;
}; // Node
template <template <typename> class TNode>
class Edge {
public:
using NODE_TYPE = TNode<Edge>; // which is TNode<Edge<TNode>>
Edge(NODE_TYPE &Source, NODE_TYPE &Sink) : m_pSource(&Source), m_pSink(&Sink) {}
NODE_TYPE const &Source() const { return *m_pSource; }
NODE_TYPE const &Sink() const { return *m_pSink; }
NODE_TYPE &Source() { return *m_pSource; }
NODE_TYPE &Sink() { return *m_pSink; }
void SetSource(NODE_TYPE &Source) { m_pSource = &Source; }
void SetSink(NODE_TYPE &Sink) { m_pSink = &Sink; }
protected:
NODE_TYPE *m_pSource;
NODE_TYPE *m_pSink;
};
Then you might have:
using MyEdge = Edge<Node>;
using MyNode = Node<Edge<Node>>; // Node<MyEdge>
or even:
template <template <typename> class TNode>
class CustomEdge : Edge<TNode> {
// ...
};
using MyNode2 = Node<CustomEdge>;
using MyEdge2 = CustomEdge<Node>;
Demo

Related

I looking for advice on whether it's better to use a friend or write a getter, but (sort of) break the encapsulation?

i have a class:
template <typename T>
class List {
private:
struct pointNode {
T data;
pointNode* next;
pointNode* prev;
pointNode() :data(0), next(nullptr), prev(nullptr) {}
pointNode(T n_data) : data(n_data), next(nullptr), prev(nullptr) {}
const T& getValue() {
return this->data;
}
};
pointNode* head;
pointNode* tail;
public:
class Iterator {
//friend class List;
using Iterator_type = List<T>::pointNode;
public:
Iterator(Iterator_type* rNode) {
current_node = rNode;
}
bool operator !=(const Iterator& pNode) {
return this->current_node != pNode.current_node;
}
T const get_value() {
return this->current_node->data;
}
private:
Iterator_type* current_node;
};
List() : head(nullptr), tail(nullptr) {}
inline void InsertFront(T&& val);
inline void InsertBack(T&& val);
inline bool is_empty();
inline void Insert_after_v(T&&searchVal,T&&val);
inline void Insert_after_p(int pos, T&& val);
};
I'm trying to write a function that inserts an element after a given:
template<typename T>
inline void List<T>::Insert_after(Iterator pos, T&& val)
{
pointNode* new_node = new pointNode(std::move(val), ... );
}
as the 2 parameters of the constructor pointNode,i need to get the value of the iterator
private:
Iterator_type* current_node;
And I think it's the right thing to do ?make a friend:
struct pointNode {
friend class Iterator
But I often see that friend is not a particularly good style.
class Iterator
{
public:
Iterator_type* get_current_node() const {
return current_node;
}
or write something like this :
class Iterator
{
public:
Iterator_type* get_node() const {
return current_node;
}
But it seems to me that it is not very good to let the user get private data.So maybe who knows how to do the right thing in such situations?I would be grateful for your advice

std::variant 'attempting to reference a deleted function'

When trying to compile I get the error: Error C2280 'std::variant<Tree::Position<int>,Tree::Position<std::string>,Tree::Position<double>,Tree::Position<bool>>::variant(const std::variant<Tree::Position<int>,Tree::Position<std::string>,Tree::Position<double>,Tree::Position<bool>> &)': attempting to reference a deleted function
I have a Tree class with a template sub-class Position. When using the load function of the Tree class, an instance of the Position class is added to the Tree's treePositionList. Also in the constructor function of the Position class an instance of the Position class is added to its childenList. I think the problem is related to the adding of an instance to those lists, though I do not understand what goes wrong exactly.
template <typename E>
class Node {
private:
string column;
E element;
public:
Node(E el, string col) { element = el; column = col;}
};
class Tree {
public:
template <typename E>
class Position {
private:
typedef variant<Position<int>, Position<string>, Position<double>, Position<bool>> Position_ManyTypes;
typedef list<Position_ManyTypes> PositionList;
Node<E>* node;
PositionList childrenList;
public:
Position(Tree* tree, const E element, const string column, const Json::Value &children);
Position(Position<E>& position);
~Position();
Position<E>& operator=(const Position<E> &position);
friend class Tree;
};
typedef variant<Position<int>, Position<string>, Position<double>, Position<bool>> Position_ManyTypes;
typedef list<Position_ManyTypes> PositionList;
Tree() {}
bool load(string filename);
private:
PositionList treePositionList;
};
bool Tree::load(string filename) {
ifstream rules_file(filename, ifstream::in);
Json::Value rules;
rules_file >> rules;
rules_file.close();
Position<string> root_position = Position<string>(this, "string123", "string456", rules["children"]);
treePositionList.push_back(root_position);
return true;
}
template <typename E>
Tree::Position<E>::Position(Tree::Position<E>& position) {
node = position.node;
childrenList = position.childrenList;
}
template <typename E>
Tree::Position<E>& Tree::Position<E>::operator=(const Tree::Position<E>& position) {
if (this != &position) {
delete node;
node = position.node;
childrenList = position.childrenList;
}
return *this;
}
template <typename E>
Tree::Position<E>::~Position() {
delete node;
}
template <typename E>
Tree::Position<E>::Position(Tree* tree, const E el, const string col, const Json::Value &children) {
node = new Node<E>(el, col);
Position<string> pos = Position<string>(tree, "string123", "string456", children[0]["children"]);
childrenList.push_back(pos);
}

"double free or corruption" of shared_ptr in binary tree

I'm writing codes about binary tree using smart pointer, but there is something wrong with destructor. I don't know where the momery leaks. But when I remove all components with parent pointer in BinNode, it runs with no errors.
Header file is showed as follow:
#ifndef BINARY_H
#define BINARY_H
#include <memory>
#include <iostream>
#ifndef RANK_DEFINED
#define RANK_DEFINED
typedef int64_t Rank;
#endif
template <typename T> class BinaryTree;
typedef enum {RB_RED, RB_BLACK} RBColor;
template <typename T>
class BinNode {
public:
friend class BinaryTree<T>;
BinNode() = default;
BinNode(const T& data, std::shared_ptr<BinNode> pare = nullptr, std::shared_ptr<BinNode> left = nullptr, std::shared_ptr<BinNode> right = nullptr,
int h = 1) : _data(data), left_child(left), right_child(right), parent(pare), height(h){}
~BinNode() = default;
std::shared_ptr<BinNode> insertAsLeft(const T& e) {
left_child = std::make_shared<BinNode>(e);
left_child->parent = std::shared_ptr<BinNode>(this);
return left_child;
}
std::shared_ptr<BinNode> insertAsRight(const T& e) {
right_child = std::make_shared<BinNode>(e, this);
return right_child;
}
int size() const;
std::shared_ptr<BinNode> succ();
template <typename VST> void travelLevel(VST (*f)());
template <typename VST> void travelPre(VST (*f)());
template <typename VST> void travelIn(VST&);
template <typename VST> void travelPost(VST&);
bool operator<(BinNode const& bn) { return _data < bn._data; }
bool operator==(BinNode const& bn) { return _data == bn._data; }
bool isRoot() { return !(parent); }
bool isLChild() { return !isRoot() && this == parent->left_child; }
bool isRChild() { return !isRoot() && this == parent->right_child; }
bool hasParent() { return !isRoot(); }
bool hasLChild() { return !left_child; }
bool hasRChild() { return !right_child; }
bool hasChild() { return hasLChild() || hasRChild(); }
bool hasBothChild() { return hasLChild() && hasRChild(); }
bool isLeaf() { return !hasChild(); }
std::shared_ptr<BinNode> sibling() const {
return (isLChild() ? parent->right_child : parent->left_child);
}
std::shared_ptr<BinNode> uncle() const {
return parent->sibling();
}
private:
T _data;
std::shared_ptr<BinNode<T>> left_child = nullptr;
std::shared_ptr<BinNode<T>> right_child = nullptr;
std::shared_ptr<BinNode<T>> parent = nullptr;
int height = 1;
};
// Binary Tree Defination
template <typename T>
class BinaryTree
{
using BN = BinNode<T>;
public:
BinaryTree(): _size(0), _root(nullptr) {}
~BinaryTree() = default;
int size() const { return _size; }
bool empty() const { return !_root; }
std::shared_ptr<BN> root() const { return _root; }
std::shared_ptr<BN> insertAsRoot(const T& e);
std::shared_ptr<BN> insertAsLC(std::shared_ptr<BN> pare, const T& e);
std::shared_ptr<BN> insertAsRC(std::shared_ptr<BN> pare, const T& e);
std::shared_ptr<BN> insertAsLC(std::shared_ptr<BN> pare, BinaryTree<T> bt);
std::shared_ptr<BN> insertAsRC(std::shared_ptr<BN> pare, BinaryTree<T> bt);
int remove(std::shared_ptr<BN>);
BinaryTree* secede(std::shared_ptr<BN>);
template <typename VST>
void travelLevel(VST& visit) { if (_root) _root->travelLevel(visit); }
template <typename VST>
void travelPre(VST& visit) { if (_root) _root->travelPre(visit); }
template <typename VST>
void travelIn(VST& visit) { if (_root) _root->travelIn(visit); }
template <typename VST>
void travelPost(VST& visit) { if (_root) _root->travelPost(visit); }
protected:
Rank _size;
std::shared_ptr<BN> _root;
virtual int updateHeight(std::shared_ptr<BN>);
void updateHeightAbove(std::shared_ptr<BN>);
void delTree(std::shared_ptr<BN>);
};
template <typename T>
std::shared_ptr<BinNode<T>> BinaryTree<T>::insertAsRoot(const T& e)
{
_root = std::make_shared<BN>(e);
_size = 1;
return _root;
}
template <typename T>
std::shared_ptr<BinNode<T>> BinaryTree<T>::insertAsLC(std::shared_ptr<BN> pare, const T& e)
{
auto newNode = pare->insertAsLeft(e);
_size++;
updateHeightAbove(newNode);
return pare->left_child;
}
template <typename T>
std::shared_ptr<BinNode<T>> BinaryTree<T>::insertAsRC(std::shared_ptr<BN> pare, const T& e)
{
}
template <typename T>
void BinaryTree<T>::updateHeightAbove(std::shared_ptr<BN> x)
{
while(x)
{
updateHeight(x);
x = x->parent;
}
}
template <typename T>
int BinaryTree<T>::updateHeight(std::shared_ptr<BN> x)
{
Rank lh = 1, rh = 1;
if (x->left_child)
lh = x->left_child->height;
if (x->right_child)
rh = x->right_child->height;
x->height = (lh > rh) ? lh : rh;
return x->height;
}
#endif
and main function is:
int main()
{
BinaryTree<int> bt;
bt.insertAsRoot(1);
bt.insertAsLC(bt.root(), 2);
cout << bt.size() << endl;
return 0;
}
Result is double free or corruption (out).
There are two problems with your parent links:
Both the downwards and the upwards pointers are std::shared_ptr. This is known as a reference cycle and prohibits your tree from ever being destroyed properly. A common solution is to make the parent pointer a std::weak_ptr, such that it does not count towards keeping the parent alive.
The second problem is hidden in your insertAsLeft: std::shared_ptr<BinNode>(this) will construct a new shared_ptr with refcount 1. This means that you have multiple shared_ptrs pointing to the same block of memory and the first one that drops to refcount 0 frees the block, leaving you with dangling pointers. Luckily for you, C++ has a ready-made solution. Simply inherit from std::enable_shared_from_this and use left_child->parent = shared_from_this(); instead. In a nutshell, this construction allows BinNode to keep track of which shared_ptr owns it.

How to declare a class friend to another before its definition

I have those two classes and I want the edge node to be friend for graph but I don't know how to declare it
The implementation of the graph is based on adjacency lists, I used unique_ptr for the auto deletion.
#ifndef GRAPH_HPP
#define GRAPH_HPP
#include <memory>
using std::unique_ptr;
template<typename Type>
class edgenode {
friend class graph<Type>; //here
public:
edgenode(Type w, int adj);
~edgenode() {};
private:
Type mWeight;
int mAdj;
unique_ptr<edgenode<Type>> mNext;
};
template<typename Type>
edgenode<Type>::edgenode(Type w, int adj)
:mWeight(Type), mAdj(adj) {
mNext = nullptr;
}
///-------------------------------------------------///
template<typename Type>
class graph {
public:
graph(int maxvertices = 1000, bool directed = false);
~graph();
private:
unique_ptr<edgenode<Type>>* mEdges;
int* mDegree;
int mNoOfNodes;
int mNoOfVertices;
int mSize;
bool m_bDirected;
};
template<typename Type>
graph<Type>::graph(int maxvertices = 1000, bool directed = false)
: mSize(maxvertices),
m_bDirected(directed),
mNoOfNodes(0),
mNoOfVertices(0)
{
mDegree = new int[mSize];
mEdges = new unique_ptr<edgenode<Type>>[mSize];
for (int i = 0; i < mSize; ++i) {
mDegree[i] = 0;
mEdges[i] = nullptr;
}
}
template<typename Type>
graph<Type>::~graph() {
delete[] mEdges;
delete[] mDegree;
}
#endif
Forward declare the class template like this:
template<typename Type> class graph;
template<typename Type>
class edgenode {
friend graph<Type>;
// ...
Add template class graph<class T>; before edgenode declaration, it's called a forward declaration:
#include <memory>
using std::unique_ptr;
template class graph<class T>; // this
template<typename Type>
class edgenode {
friend class graph<Type>; //here
public:
...

how to preserve const correctness across pointers?

I am trying to have a const operation on a class that is truly const - it does not change data that the class points to.
For example:
class Node{
public:
int val;
};
class V{
public:
Node * node; //what is the change that is needed here?
void const_action()const{
node->val=5; //error wanted here
}
void action(){
node->val=5; //error is not wanted here
}
};
You can use a template to enforce the const correctness on a pointer without changing the meaning or the implementation of your class:
template <typename T>
class PreseveConstPointer
{
T *t_;
public:
PreseveConstPointer(T *t = nullptr)
: t_(t)
{
}
PreseveConstPointer<T> * operator=(T *t)
{
t_ = t;
return this;
}
T* operator->()
{
return t_;
}
T const * operator->() const
{
return t_;
}
T * data()
{
return t_;
}
};
class Node{
public:
int val;
};
class V{
public:
PreseveConstPointer<Node> node;
V()
{
node = new Node;
}
~V()
{
if(node.data())
delete node.data();
}
void const_action()const{
node->val=5; // You will get an error here
}
void action(){
node->val=5; // No error here
}
};
const after a function declaration says that the function is not allowed to change any class members (except ones that are marked mutable).
Since your code doesn't change any class member, and only changes the object node points to, both function will compile.
AFAIK there's no way to prevent this. If you mark the node const, neither will compile.
You're confusing Node* const for Node const*.
An [unfortunate?] side effect of using indirection here is that constness of the pointer member has nothing to do with the actual Node on which you're operating.
If you don't need that member to be a pointer, then this is pleasingly easy:
class V
{
public:
Node node;
void const_action() const
{
node.val = 5; // error here
}
void action()
{
node.val = 5; // no error here
}
};
However, given its name, I suspect life is not that simple and you are basically out of luck.