Is there a library that provides a (directed) hypergraph implementation in C++? - c++
I'm currently working on a project that enumerates the k-best solutions of a dynamic program using a directed hypergraph framework. My current implementation (in Python) works well, but is fairly slow. The algorithm performs a number of tight loops and a fair bit of recursion. I really think that I could realize significant speed improvements using a C++ implementation. However, after a fair bit of searching, I was unable to find any libraries that provide hypergraph implementations in C++ (specifically directed hypergraphs -- but I was unable to find even libraries for undirected hypergraphs). Does anyone know of such a library? It seems there was a GSoC proposal to bring hypergraph support to boost a few years ago, but it looks like it didn't really pan out.
I don't know of a library, but you could roll your own.
After messing around with the code for three days, I finally got a hypermap to compile without warnings on MSVC10 and GCC(http://ideone.com/oj46o).
Declarations:
#include <map>
#include <functional>
#include <memory>
template<class V, class E=int, class PV = std::less<V>, class PE=std::less<E>, class A=std::allocator<V> >
// V is data type of vertex
// E is identifier of Edge
// PV is node sorting predicate
// PE is edge sorting predicate
// A is allocator
class hypergraph {
#if _MSC_VER <= 1600
typedef A sub_allocator;
#else
typedef std::scoped_allocator_adaptor<A> sub_allocator;
#endif
public:
class vertex;
class edge;
typedef std::map<V, vertex, PV, sub_allocator> vertexset;
typedef std::map<E, edge, PE, sub_allocator> edgeset;
typedef typename vertexset::iterator vertexiter;
typedef typename edgeset::iterator edgeiter;
typedef typename vertexset::const_iterator cvertexiter;
typedef typename edgeset::const_iterator cedgeiter;
typedef std::reference_wrapper<const V> rwv;
typedef std::reference_wrapper<const E> rwe;
typedef std::reference_wrapper<vertex> rwvertex;
typedef std::reference_wrapper<edge> rwedge;
typedef std::map<rwv, rwvertex, PV, sub_allocator> ivertexset;
typedef std::map<rwe, rwedge, PE, sub_allocator> iedgeset;
typedef typename ivertexset::iterator ivertexiter;
typedef typename iedgeset::iterator iedgeiter;
typedef typename ivertexset::const_iterator civertexiter;
typedef typename iedgeset::const_iterator ciedgeiter;
class vertex {
friend class hypergraph<V,E,PV,PE,A>;
iedgeset edges_;
vertex(const PE&, const sub_allocator&);/* so users can'V make their own vertices*/
public:
vertex(vertex&&);
vertex& operator=(vertex&&);
iedgeset& edges();
const iedgeset& edges() const;
};
class edge {
friend class hypergraph<V,E,PV,PE,A>;
ivertexset vertices_;
ivertexiter head_;
edge(const PV&, const sub_allocator&); /* so users can'V make their own edges*/
public:
edge(edge&&);
edge& operator=(edge&&);
void set_head(const V& v);
const V* get_head() const;
ivertexset& vertices();
const ivertexset& vertices() const;
};
hypergraph(const PV& vertexpred=PV(), const PE& edgepred=PE(), const A& alloc=A());
std::pair<vertexiter,bool> add_vertex(V v=V());
std::pair<edgeiter,bool> add_edge(E e=E());
vertexiter erase_vertex(const vertexiter& iter);
vertexiter erase_vertex(const V& rhs);
edgeiter erase_edge(const edgeiter& iter);
edgeiter erase_edge(const E& rhs);
void connect(const E& e, const V& v);
void connect(const edgeiter& ei, const vertexiter& vi);
void disconnect(const E& e, const V& v);
void disconnect(const edgeiter& ei, const vertexiter& vi);
vertexset& vertices();
const vertexset& vertices() const;
edgeset& edges();
const edgeset& edges() const;
A get_allocator() const;
protected:
hypergraph(const hypergraph& rhs);
hypergraph& operator=(const hypergraph& rhs);
PV pv_;
PE pe_;
A a_;
vertexset vertices_;
edgeset edges_;
};
namespace std {
template<class E, class T, class R>
std::basic_ostream<E,T>& operator<<(std::basic_ostream<E,T>& s, const std::reference_wrapper<R>& r);
template<class E, class T, class R>
std::basic_istream<E,T>& operator>>(std::basic_istream<E,T>& s, std::reference_wrapper<R>& r);
}
Definitions:
#include <algorithm>
#include <cassert>
template<class V, class E, class PV, class PE, class A>
inline hypergraph<V,E,PV,PE,A>::vertex::vertex(const PE& pred, const typename hypergraph<V,E,PV,PE,A>::sub_allocator& alloc)
: edges_(pred, alloc)
{}
template<class V, class E, class PV, class PE, class A>
inline hypergraph<V,E,PV,PE,A>::vertex::vertex(typename hypergraph<V,E,PV,PE,A>::vertex&& rhs)
: edges_(std::move(rhs.edges_))
{}
template<class V, class E, class PV, class PE, class A>
inline typename hypergraph<V,E,PV,PE,A>::vertex& hypergraph<V,E,PV,PE,A>::vertex::operator=(typename hypergraph<V,E,PV,PE,A>::vertex&& rhs)
{
edges_ = std::move(rhs);
return *this;
}
template<class V, class E, class PV, class PE, class A>
inline typename hypergraph<V,E,PV,PE,A>::iedgeset& hypergraph<V,E,PV,PE,A>::vertex::edges()
{return edges_;}
template<class V, class E, class PV, class PE, class A>
inline const typename hypergraph<V,E,PV,PE,A>::iedgeset& hypergraph<V,E,PV,PE,A>::vertex::edges() const
{return edges_;}
template<class V, class E, class PV, class PE, class A>
inline hypergraph<V,E,PV,PE,A>::edge::edge(const PV& pred, const typename hypergraph<V,E,PV,PE,A>::sub_allocator& alloc)
: vertices_(pred, alloc)
, head_(vertices_.end())
{}
template<class V, class E, class PV, class PE, class A>
inline hypergraph<V,E,PV,PE,A>::edge::edge(edge&& rhs)
: vertices_(rhs.vertices_)
, head_(rhs.head_!=rhs.vertices_.end() ? vertices_.find(rhs.head_->first) : vertices_.end())
{}
template<class V, class E, class PV, class PE, class A>
inline typename hypergraph<V,E,PV,PE,A>::edge& hypergraph<V,E,PV,PE,A>::edge::operator=(typename hypergraph<V,E,PV,PE,A>::edge&& rhs)
{
vertices_ = std::move(rhs);
if (rhs.head_ != rhs.vertices_.end())
head_ = vertices_.find(rhs.head_->first);
else
head_ = vertices_.end();
return *this;
}
template<class V, class E, class PV, class PE, class A>
inline void hypergraph<V,E,PV,PE,A>::edge::set_head(const V& v)
{
ivertexiter iter = vertices_.find(std::ref(v));
assert(iter != vertices_.end());
head_ = iter;
}
template<class V, class E, class PV, class PE, class A>
inline const V* hypergraph<V,E,PV,PE,A>::edge::get_head() const
{return (head_ != vertices_.end() ? &head_->first.get() : NULL);}
template<class V, class E, class PV, class PE, class A>
inline const typename hypergraph<V,E,PV,PE,A>::ivertexset& hypergraph<V,E,PV,PE,A>::edge::vertices() const
{ return vertices_; }
template<class V, class E, class PV, class PE, class A>
inline typename hypergraph<V,E,PV,PE,A>::ivertexset& hypergraph<V,E,PV,PE,A>::edge::vertices()
{ return vertices_; }
template<class V, class E, class PV, class PE, class A>
inline hypergraph<V,E,PV,PE,A>::hypergraph(const PV& vertexpred, const PE& edgepred, const A& alloc)
:pv_(vertexpred)
,pe_(edgepred)
,a_(alloc)
,vertices_(vertexpred, a_)
,edges_(edgepred, a_)
{}
template<class V, class E, class PV, class PE, class A>
inline std::pair<typename hypergraph<V,E,PV,PE,A>::vertexiter, bool> hypergraph<V,E,PV,PE,A>::add_vertex(V v)
{ return vertices_.insert(std::pair<V, vertex>(std::move(v),vertex(pe_, a_))); }
template<class V, class E, class PV, class PE, class A>
inline std::pair<typename hypergraph<V,E,PV,PE,A>::edgeiter, bool> hypergraph<V,E,PV,PE,A>::add_edge(E e)
{ return edges_.insert(std::pair<E,edge>(std::move(e), edge(pv_, a_))); }
template<class V, class E, class PV, class PE, class A>
inline typename hypergraph<V,E,PV,PE,A>::vertexiter hypergraph<V,E,PV,PE,A>::erase_vertex(const typename hypergraph<V,E,PV,PE,A>::vertexiter& iter)
{
for(auto i = iter->edges().begin(); i != iter->edges().end(); ++i)
i->erase(*iter);
return vertices_.erase(iter);
}
template<class V, class E, class PV, class PE, class A>
inline typename hypergraph<V,E,PV,PE,A>::vertexiter hypergraph<V,E,PV,PE,A>::erase_vertex(const V& rhs)
{
vertexiter vi = vertices_.find(rhs);
assert(vi != vertices_.end());
vertex& v = vi->second;
for(auto i = v.edges().begin(); i != v.edges().end(); ++i)
i->second.get().vertices_.erase(std::ref(vi->first));
return vertices_.erase(vi);
}
template<class V, class E, class PV, class PE, class A>
inline typename hypergraph<V,E,PV,PE,A>::edgeiter hypergraph<V,E,PV,PE,A>::erase_edge(const typename hypergraph<V,E,PV,PE,A>::edgeiter& iter)
{
for(auto i = iter->vertices().begin(); i != iter->vertices().end(); ++i)
i->edges_.erase(*iter);
return edges_.erase(iter);
}
template<class V, class E, class PV, class PE, class A>
inline typename hypergraph<V,E,PV,PE,A>::edgeiter hypergraph<V,E,PV,PE,A>::erase_edge(const E& rhs)
{
edgeiter ei = edges_.find(rhs);
assert(ei != edges_.end());
edge& e = ei->second;
for(auto i = e.vertices().begin(); i != e.vertices().end(); ++i)
i->second.get().edges_.erase(std::ref(ei->first));
return edges_.erase(ei);
}
template<class V, class E, class PV, class PE, class A>
inline void hypergraph<V,E,PV,PE,A>::connect(const E& e, const V& v)
{
vertexiter vi = vertices_.find(v);
edgeiter ei = edges_.find(e);
assert(vi != vertices_.end());
assert(ei != edges_.end());
vi->second.edges_.insert(typename iedgeset::value_type(std::ref(ei->first), std::ref(ei->second)));
auto n = ei->second.vertices_.insert(typename ivertexset::value_type(std::ref(vi->first), std::ref(vi->second)));
if (ei->second.vertices_.size()==1)
ei->second.head_ = n.first;
}
template<class V, class E, class PV, class PE, class A>
inline void hypergraph<V,E,PV,PE,A>::connect(const typename hypergraph<V,E,PV,PE,A>::edgeiter& ei, const typename hypergraph<V,E,PV,PE,A>::vertexiter& vi)
{
assert(std::distance(vertices_.begin(), vi)>=0); //actually asserts that the iterator belongs to this container
assert(std::distance(edges_.begin(), ei)>=0); //actually asserts that the iterator belongs to this container
vi->edges_.insert(typename iedgeset::value_type(std::ref(ei->first), std::ref(ei->second)));
auto n = ei->vertices_.insert(typename ivertexset::value_type(std::ref(vi->first), std::ref(vi->second)));
if (ei->second.verticies_.size()==1)
ei->second.head_ = n.first;
}
template<class V, class E, class PV, class PE, class A>
inline void hypergraph<V,E,PV,PE,A>::disconnect(const E& e, const V& v)
{
edgeiter ei = edges_.find(e);
vertexiter vi = vertices_.find(v);
assert(ei != edges.end());
assert(vi != vertices_.end());
if (ei->head_.first == v) {
if (ei->head_ != ei->vertices.begin())
ei->head = ei->vertices.begin();
else
ei->head = ei->vertices.end();
}
ei->vertices_.erase(std::ref(vi->first));
vi->edges_.erase(std::ref(ei->first));
}
template<class V, class E, class PV, class PE, class A>
inline void hypergraph<V,E,PV,PE,A>::disconnect(const typename hypergraph<V,E,PV,PE,A>::edgeiter& ei, const typename hypergraph<V,E,PV,PE,A>::vertexiter& vi)
{
assert(std::distance(edges_.begin(), ei)>=0); //actually asserts that the iterator belongs to this container
assert(std::distance(vertices_.begin(), vi)>=0); //actually asserts that the iterator belongs to this container
if (ei->head_.first == vi->first) {
if (ei->head_ != ei->vertices.begin())
ei->head = ei->vertices.begin();
else
ei->head = ei->vertices.end();
}
ei->vertices_.erase(std::ref(vi->first));
vi->edges_.erase(std::ref(ei->first));
}
template<class V, class E, class PV, class PE, class A>
inline typename hypergraph<V,E,PV,PE,A>::vertexset& hypergraph<V,E,PV,PE,A>::vertices()
{ return vertices_;}
template<class V, class E, class PV, class PE, class A>
inline const typename hypergraph<V,E,PV,PE,A>::vertexset& hypergraph<V,E,PV,PE,A>::vertices() const
{ return vertices_;}
template<class V, class E, class PV, class PE, class A>
inline typename hypergraph<V,E,PV,PE,A>::edgeset& hypergraph<V,E,PV,PE,A>::edges()
{ return edges_;}
template<class V, class E, class PV, class PE, class A>
inline const typename hypergraph<V,E,PV,PE,A>::edgeset& hypergraph<V,E,PV,PE,A>::edges() const
{ return edges_;}
template<class V, class E, class PV, class PE, class A>
inline A hypergraph<V,E,PV,PE,A>::get_allocator() const
{ return a_;}
namespace std {
template<class E, class T, class R>
std::basic_ostream<E,T>& operator<<(std::basic_ostream<E,T>& s, const std::reference_wrapper<R>& r)
{return s << r.get();}
template<class E, class T, class R>
std::basic_istream<E,T>& operator>>(std::basic_istream<E,T>& s, std::reference_wrapper<R>& r)
{return s >> r.get();}
}
Note that this is not thoroughly tested, but it compiles and ran through my mini-suite without errors. (As shown in the IDEOne link). The Vertex types and the Edge identifiers can be any types you want, I tested with int verteces and string edge identifiers.
Hypergraphs are used for decoding in statistical machine translation. There are implementations of hypergraph data structures and algorithms in cdec decoder or relax-decode
One limitation is the edges in these implementation have multiple tails node but only a single head node.
Related
Overriding multiplication operator for different datatypes
I created the following class template<typename T, typename S> class Polar{ private: T rad; S phi; public: Polar(T r, S p) : rad{r}, phi{p} {} template<typename A, typename B> friend std::ostream& operator<<(std::ostream&, const Polar<A,B>&); And i want to override the multiplication function for different datatypes for example int and double like this int main() { Polar<int,int> p{2,3}; Polar<int,int> w{4,5}; Polar<float,double> z{6,7}; std::cout << p*w << std::endl; std::cout << p*z << std::endl; return 0; } I declared the function as a friend in Polar like this: template<typename A, typename B, typename C, typename D> friend auto operator*(const Polar<A,B>&, const Polar<C,D>&); And then implemented it as the following: template<typename A, typename B, typename C, typename D> auto operator*(const Polar<A,B>& p, const Polar<C,D>& w) -> Polar<decltype(p.rad * w.rad),decltype(p.phi + w.phi)> { return Polar<decltype(p.rad * w.rad),decltype(p.phi + w.phi)> {(p.rad * w.rad),(p.phi + w.phi)}; } But i am getting an error because of using auto before deduction. I dont know how to get the return-type to work and i dont want to write a function for each possible combination. Is there an easy way to tackle this?
Okay i got it i only needed to change the declartion in class from template<typename A, typename B, typename C, typename D> friend auto operator*(const Polar<A,B>&, const Polar<C,D>&); To the following: template<typename A, typename B, typename C, typename D> friend auto operator*(const Polar<A,B>& p, const Polar<C,D>& w) -> Polar<decltype(p.rad * w.rad),decltype(p.phi + w.phi)>;
Using std::common_type_t> may serve here. It gives you "the common type among all types T..., that is the type all T... can be implicitly converted to." [Demo] #include <iostream> // cout #include <ostream> #include <type_traits> // common_type_t template<typename T, typename S> class Polar { public: Polar(T r, S p) : rad{r}, phi{p} {} template<typename A, typename B> friend std::ostream& operator<<(std::ostream&, const Polar<A, B>&); template<typename A, typename B, typename C, typename D> friend auto operator*(const Polar<A, B>& p, const Polar<C, D>& w); private: T rad; S phi; }; template<typename A, typename B> std::ostream& operator<<(std::ostream& os, const Polar<A, B>& p) { return os << "(" << p.rad << ", " << p.phi << ")"; } template<typename A, typename B, typename C, typename D> auto operator*(const Polar<A, B>& p, const Polar<C, D>& w) { using RT = std::common_type_t<A, C>; using RS = std::common_type_t<B, D>; return Polar<RT, RS>{p.rad * w.rad, p.phi + w.phi}; } int main() { Polar<int, int> p{2, 3}; Polar<int, int> w{4, 5}; Polar<float, double> z{6.5, 7.5}; std::cout << p* w << std::endl; std::cout << p*z << std::endl; }
Why my UniqPtr objects are double in size compared to std::unique_ptr?
In real applications I should stick to the standard library facilities, for practice and understanding how those facilities work I should try to implement my own. Here I've implemented a simulation of the smart pointer unique_ptr: #include<iostream> #include <memory> template <typename T> class DefDel { public: template <typename U> void operator()(U* p)const { std::cout << "freeing memory...\n"; delete p; } }; template <typename T> class DefDel<T[]> { public: template <typename U> void operator()(U* p)const { std::cout << "freeing memory of an array of objects...\n"; delete[] p; } }; template <typename T, typename D = DefDel<T>> class UniqPtr final { public: UniqPtr(T* = nullptr, D = DefDel<T>{}); UniqPtr(UniqPtr const&) = delete; UniqPtr(UniqPtr&&) noexcept; UniqPtr& operator =(UniqPtr const&) = delete; UniqPtr& operator =(UniqPtr&&) noexcept; ~UniqPtr(); T& operator*(); T const& operator*() const; T* operator->(); T const* operator->() const; operator bool() const; private: T* ptr_{nullptr}; D del_{}; }; template <typename T, typename D> UniqPtr<T, D>::UniqPtr(T* p, D del) : ptr_(p), del_(del) {} template <typename T, typename D> UniqPtr<T, D>::UniqPtr(UniqPtr&& rhs) noexcept : ptr_(std::move(rhs.ptr_)), del_(std::move(rhs.del_)) { rhs.ptr_ = nullptr; } template <typename T, typename D> UniqPtr<T, D>& UniqPtr<T, D>::operator = (UniqPtr&& rhs) noexcept { if(this != &rhs) { ptr_ = std::move(rhs.ptr_); del_ = std::move(rhs.del_); rhs.ptr_ = nullptr; } return *this; } template <typename T, typename D> UniqPtr<T, D>::~UniqPtr() { del_(ptr_); } template <typename T, typename D> T& UniqPtr<T, D>::operator*() { return *ptr_; } template <typename T, typename D> T const& UniqPtr<T, D>::operator*() const { return *ptr_; } template <typename T, typename D> T* UniqPtr<T, D>::operator->() { return ptr_; } template <typename T, typename D> T const* UniqPtr<T, D>::operator->() const { return ptr_; } template <typename T, typename D> UniqPtr<T, D>::operator bool() const { return ptr_; } // for array template <typename T, typename D> class UniqPtr<T[], D> final { public: UniqPtr(T* = nullptr, D = DefDel<T[]>{}); UniqPtr(UniqPtr const&) = delete; UniqPtr(UniqPtr&&) noexcept; UniqPtr& operator =(UniqPtr const&) = delete; UniqPtr& operator =(UniqPtr&&) noexcept; ~UniqPtr(); T& operator*(); T const& operator*() const; T* operator->(); T const* operator->() const; operator bool() const; private: T* ptr_{nullptr}; D del_{}; }; template <typename T, typename D> UniqPtr<T[], D>::UniqPtr(T* p, D del) : ptr_(p), del_(del) {} template <typename T, typename D> UniqPtr<T[], D>::UniqPtr(UniqPtr&& rhs) noexcept : ptr_(std::move(rhs.ptr_)), del_(std::move(rhs.del_)) { rhs.ptr_ = nullptr; } template <typename T, typename D> UniqPtr<T[], D>& UniqPtr<T[], D>::operator = (UniqPtr&& rhs) noexcept { if(this != &rhs) { ptr_ = std::move(rhs.ptr_); del_ = std::move(rhs.del_); rhs.ptr_ = nullptr; } return *this; } template <typename T, typename D> UniqPtr<T[], D>::~UniqPtr() { del_(ptr_); } template <typename T, typename D> T& UniqPtr<T[], D>::operator*() { return *ptr_; } template <typename T, typename D> T const& UniqPtr<T[], D>::operator*() const { return *ptr_; } template <typename T, typename D> T* UniqPtr<T[], D>::operator->() { return ptr_; } template <typename T, typename D> T const* UniqPtr<T[], D>::operator->() const { return ptr_; } template <typename T, typename D> UniqPtr<T[], D>::operator bool() const { return ptr_; } int main() { UniqPtr<int[]> upi(new int[3]{57}); std::cout << sizeof(upi) << '\n'; std::unique_ptr<int[], DefDel<int[]>> upi2(new int[3]{57}); std::cout << sizeof(upi2) << '\n'; } Why the size of my UniqPtr objects are double in size as std::unique_ptr (even being initialized with the same values)? Is that because of my class is storing a Del_ object as a member? If that is the problem then how could I achieve the very similar behavior as unique_ptr with 0 cost?
Why the size of my UniqPtr objects are double in size as std::unique_ptr (even being initialized with the same values)? Is that because of my class is storing a Del_ object as a member? Yes. Because D del_ needs to have storage, and every T* ptr_ has to be properly aligned If that is the problem then how could I achieve the very similar behavior as unique_ptr with 0 cost? You can privately derive from D, rather than have it be a member. Then an instantiation with an empty class may cave it occupy no extra storage. template <typename T, typename D = DefDel<T>> class UniqPtr final : D { public: UniqPtr(T* = nullptr, D = {}); UniqPtr(UniqPtr const&) = delete; UniqPtr(UniqPtr&&) noexcept; UniqPtr& operator =(UniqPtr const&) = delete; UniqPtr& operator =(UniqPtr&&) noexcept; ~UniqPtr(); T& operator*(); T const& operator*() const; T* operator->(); T const* operator->() const; operator bool() const; private: T* ptr_{nullptr}; }; template <typename T, typename D> UniqPtr<T, D>::UniqPtr(T* p, D del) : D(del), ptr_(p) {} template <typename T, typename D> UniqPtr<T, D>::UniqPtr(UniqPtr&& rhs) noexcept : D(std::move(*rhs)), ptr_(std::exchange(rhs.ptr_, nullptr)) {} template <typename T, typename D> UniqPtr<T, D>& UniqPtr<T, D>::operator = (UniqPtr&& rhs) noexcept { using std::swap; swap(static_cast<D&>(*this), static_cast<D&>(rhs)); swap(ptr_, rhs.ptr_); } template <typename T, typename D> UniqPtr<T, D>::~UniqPtr() { static_cast<D&>(*this)(ptr_); }
How should I use expression templates in order to implement scalar multiplication for a mathematical vector class
Please consider the following (partial) implementation of a mathematical vector class (which is basically the code you can find in the Wikipedia article about expression templates): namespace math { template<class E> class vector_expression { public: std::size_t size() const { return static_cast<E const&>(*this).size(); } double operator[](size_t i) const { if (i >= size()) throw std::length_error(""); return static_cast<E const&>(*this)[i]; } operator E&() { return static_cast<E&>(*this); } operator E const&() const { return static_cast<E const&>(*this); } }; // class vector_expression template<class E1, class E2> class vector_sum : public vector_expression<vector_sum<E1, E2>> { public: vector_sum(vector_expression<E1> const& e1, vector_expression<E2> const& e2) : m_e1(e1), m_e2(e2) { if (e1.size() != e2.size()) throw std::logic_error(""); } std::size_t size() const { return m_e1.size(); // == m_e2.size() } double operator[](std::size_t i) const { return m_e1[i] + m_e2[i]; } private: E1 const& m_e1; E2 const& m_e2; }; // class vector_sum template<typename E1, typename E2> vector_sum<E1, E2> operator+(vector_expression<E1> const& e1, vector_expression<E2> const& e2) { return { e1, e2 }; } template<typename T> class vector : public vector_expression<vector<T>> { public: vector(std::size_t d) : m_data(d) { } vector(std::initializer_list<T> init) : m_data(init) { } template<class E> vector(vector_expression<E> const& expression) : m_data(expression.size()) { for (std::size_t i = 0; i < expression.size(); ++i) m_data[i] = expression[i]; } std::size_t size() const { return m_data.size(); } double operator[](size_t i) const { return m_data[i]; } double& operator[](size_t i) { return m_data[i]; } private: std::vector<T> m_data; }; // class vector } // namespace math How should I extend this implementation to allow the following operations: vector<double> x = { ... }; auto y = 4711 * x; // or y = x * 4711 auto z = 1 + x; // or x + 1, which should yield z[i] = x[i] + 1 I suppose that I need something like namespace math { template<class E, typename T> class vector_product : public vector_expression<vector_product<E, T>> { public: vector_product(vector_expression<E> const& e, T const& t) : m_e(e), m_t(t) { } std::size_t size() const { return m_e.size(); } double operator[](std::size_t i) const { return m_e[i] * m_t; } private: E const& m_e; T const& m_t; }; // class vector_product template<class E, typename T> vector_product<E, T> operator*(vector_expression<E> const& e, T const& t) { return { e, t }; } template<class E, typename T> vector_product<E, T> operator*(T const& t, vector_expression<E> const& e) { return e * t; } } // namespace math but I don't know whether or not this is a good approach. So, how should I do it? And should I add any copy or move constructor/assignment operator? I guess not, since the implicit ones should do a perfect job, cause the only member variable of vector is a STL type.
I would simply extend vector_sum to allow for E2 to be double, and to handle that gracefully if it is. This would involve taking arguments of E1 const& and E2 const& in your constructor, potentially not throwing on size differences (since scalars have no size), and rewriting operator[] to not do indexing. For the last part, something like: double operator[](std::size_t i) const { return m_e1[i] + get(m_e2, i); } private: template <class E> double get(E const& rhs, std::size_t i) const { return rhs[i]; } double get(double scalar, std::size_t ) const { return scalar; } This way, if you're adding two vector_expressions, you'll do the indexing, but if you're adding a vector_expression and a double - you won't even try to index into the double. This switch happens at compile-time, so there's no run-time overhead. Then, all you just need to add a couple more operator+s: template <typename E1> vector_sum<E1, double> operator+(vector_expression<E1> const& e1, double d) { return {e1, d}; } template <typename E1> vector_sum<E1, double> operator+(double d, vector_expression<E1> const& e1) { return {e1, d}; } Which lets you write: math::vector<int> x = {1, 2, 3, 4}; math::vector<int> y = {2, 3, 4, 5}; auto sum = 3 + x + 1; Keeping references to const probably isn't the right thing to do - if you did a+b+c, you'll end up keeping a reference to the temporary a+b. You probably only want to keep a reference to the actual vector, and keep copies of all the intermediate objects. To support vector_product, you'll probably want vector_sum<E1,E2> to really be vector_binary_op<E1,E2,std::plus<>> and then vector_product<E1,E2> should be vector_binary_op<E1,E2,std::multiplies<>>. That way, you won't have all the duplication.
Inspired by Yakk and Barry, I've finally come up with the following: namespace math { template<class E> class expression { public: auto size() const { return static_cast<E const&>(*this).size(); } auto operator[](std::size_t i) const { if (i >= size()) throw std::length_error(""); return static_cast<E const&>(*this)[i]; } operator E&() { return static_cast<E&>(*this); } operator E const&() const { return static_cast<E const&>(*this); } }; // class expression template<typename T, class Allocator = std::allocator<T>> class vector : public expression<vector<T>> { private: using data_type = std::vector<T, Allocator>; data_type m_data; public: using value_type = T; using allocator_type = Allocator; using size_type = typename data_type::size_type; using difference_type = typename data_type::difference_type; using reference = typename data_type::reference; using const_reference = typename data_type::const_reference; using pointer = typename data_type::pointer ; using const_pointer = typename data_type::const_pointer; vector(size_type d) : m_data(d) { } vector(std::initializer_list<value_type> init) : m_data(init) { } template<class E> vector(expression<E> const& expression) : m_data(expression.size()) { for (size_type i = 0; i < expression.size(); ++i) m_data[i] = expression[i]; } size_type size() const { return m_data.size(); } value_type operator[](size_type i) const { return m_data[i]; } value_type& operator[](size_type i) { return m_data[i]; }; }; // class vector namespace detail { template<typename T> class scalar : public expression<scalar<T>> { public: using value_type = T; using allocator_type = std::allocator<void>; using size_type = typename std::allocator<T>::size_type; using difference_type = typename std::allocator<T>::difference_type; using reference = typename std::allocator<T>::reference; using const_reference = typename std::allocator<T>::const_reference; using pointer = typename std::allocator<T>::pointer; using const_pointer = typename std::allocator<T>::const_pointer; scalar(value_type value) : m_value(value) { } size_type size() const { return 0; } operator value_type&() { return static_cast<value_type&>(*this); } operator value_type const&() const { return static_cast<value_type const&>(*this); } value_type operator[](size_type i) const { return m_value; } value_type& operator[](size_type i) { return m_value; } private: value_type m_value; }; // class scalar template<class> struct is_scalar : std::false_type { }; template<class T> struct is_scalar<scalar<T>> : std::true_type { }; } // namespace detail template<class E1, class E2, class BinaryOperation> class vector_binary_operation : public expression<vector_binary_operation<E1, E2, BinaryOperation>> { public: using value_type = decltype(BinaryOperation()(typename E1::value_type(), typename E2::value_type())); using allocator_type = std::conditional_t< detail::is_scalar<E1>::value, typename E2::allocator_type::template rebind<value_type>::other, typename E1::allocator_type::template rebind<value_type>::other>; private: using vector_type = vector<value_type, allocator_type>; public: using size_type = typename vector_type::size_type; using difference_type = typename vector_type::difference_type; using reference = typename vector_type::reference; using const_reference = typename vector_type::const_reference; using pointer = typename vector_type::pointer; using const_pointer = typename vector_type::const_pointer; vector_binary_operation(expression<E1> const& e1, expression<E2> const& e2, BinaryOperation op) : m_e1(e1), m_e2(e2), m_op(op) { if (e1.size() > 0 && e2.size() > 0 && !(e1.size() == e2.size())) throw std::logic_error(""); } size_type size() const { return m_e1.size(); // == m_e2.size() } value_type operator[](size_type i) const { return m_op(m_e1[i], m_e2[i]); } private: E1 m_e1; E2 m_e2; //E1 const& m_e1; //E2 const& m_e2; BinaryOperation m_op; }; // class vector_binary_operation template<class E1, class E2> vector_binary_operation<E1, E2, std::plus<>> operator+(expression<E1> const& e1, expression<E2> const& e2) { return{ e1, e2, std::plus<>() }; } template<class E1, class E2> vector_binary_operation<E1, E2, std::minus<>> operator-(expression<E1> const& e1, expression<E2> const& e2) { return{ e1, e2, std::minus<>() }; } template<class E1, class E2> vector_binary_operation<E1, E2, std::multiplies<>> operator*(expression<E1> const& e1, expression<E2> const& e2) { return{ e1, e2, std::multiplies<>() }; } template<class E1, class E2> vector_binary_operation<E1, E2, std::divides<>> operator/(expression<E1> const& e1, expression<E2> const& e2) { return{ e1, e2, std::divides<>() }; } template<class E, typename T> vector_binary_operation<E, detail::scalar<T>, std::divides<>> operator/(expression<E> const& expr, T val) { return{ expr, detail::scalar<T>(val), std::divides<>() }; } template<class E, typename T> vector_binary_operation<E, detail::scalar<T>, std::multiplies<>> operator*(T val, expression<E> const& expr) { return{ expr, detail::scalar<T>(val), std::multiplies<>() }; } template<class E, typename T> vector_binary_operation<E, detail::scalar<T>, std::multiplies<>> operator*(expression<E> const& expr, T val) { return{ expr, detail::scalar<T>(val), std::multiplies<>() }; } } // namespace math This allows the operations +, -, /, * for two vectors of the same size as well as the multiplication a * x and x * a of a vector x and a value a. Moreover, we can divide x / a, but not a / x (since that makes no sense). I think that this is the most plausible solution. However, there are still some issues: I've added an Allocator template parameter to the vector class. In vector_binary_operation I need to know the resulting vector type. It would be possible that both expressions have a different allocator_type. I've decided to choose the allocator_type of the first expression in vector_binary_operation. I don't think that this is a real problem, since I don't think that it would make much sense to use different Allocators in this scenario. The bigger issue is that I don't know how I need to deal with the expression member variables in vector_binary_operation. It would make sense to declare them as const references, cause the whole point of the code is to avoid unnecessary copies. However, as Barry pointed out, if we write sum = a + b + c, we will end up keeping a reference to the temporary a + b. Doing sum[0] will call operator()[0] on that temporary. But that object was deleted after the previous line. I don't know what I need to do here and asked another question.
Make a specializattion of template<class E> class vector_expression for E=double. Add in concept of min/max size (as doubles are 0 to infinite dimension), and maybe is_scalar (might not be needed, except cast-to-scalar?). Kill vector_sum and take its toys. Make binop_vector that takes a elementop on the elements. Add friend binop_vector<vector_expression,R,ElemAdd> operator+( vector_expression const& l. R const& r ){ return {l,r}; } friend binop_vector<vector_expression<double>,vector_expression,ElemAdd> operator+( double const& l. vector_expression const const& r ){ return {l,r}; } And similar for * with ElemMult to vector_expression. Ths binop uses the element wise operation to implement []. Change asserts to ensure you have overlapping min/max sizes. Report intersection in binop_vector. The vector_expression<double> overload has a min 0 max -1 (size_t) and always returns its value. If you need more than one scalar type, write scalar_expression<T> and inherit vector_expression<double> (etc) from it. The above is not tested, just written on phone as I sit in garage.
how to initialise this templated pair in c++?
template<class V, class E> class G { public: G(); void InsertVertex(const V&); void InsertEdge(const V&, const V&, const E& ); private: typedef set<V,less<V> > vSet; typedef pair<const V,V> ePair; typedef multimap<V,V,less<V> > eSet; typedef map<ePair,E, less<ePair> > edgeValueMap; vSet vertices; eSet edges; edgeValueMap edgeVals; }; template<class V,class E> G<V,E>::G(){} template<class V,class E> void G<V,E>::InsertVertex(const V& a) { vertices.insert(a); } template<class V,class E> void G<V,E>::InsertEdge(const V& a,const V& b, const E& val) { //create a pair ePair<const V,v> e(a,b); edges.insert(e); edgeVals.insert(e,val); } int main() { G<char,int> g; g.InsertVertex('a'); g.InsertVertex('b'); g.InsertVertex('c'); g.InsertEdge('a','b',1); return 0; } while i create a pair using "ePair e(a,b)" i am getting error: "template2.cpp:39:2: error: ‘G::ePair’ is not a template" I am not sure exactly why this compile error is coming? am i missing anything here?
I was using make_pair to build actual map entries, it works here. But note that also the call edgeVals.insert(e,val); give error: so i modified that also: template<class V,class E> void G<V,E>::InsertEdge(const V& a,const V& b, const E& val) { //create a pair ePair e = make_pair(a,b); edges.insert(e); edgeVals[e] = val; }
unable to match function definition to an existing declaration
While attempting to provide an implimentation to this question, I got stuck on an error. I'm getting errors for 8 of my functions like this: f:\code\utilities\hypergraph\hypergraph\hypergraph.h(233): error C2244: 'hypergraph::add_edge' : unable to match function definition to an existing declaration f:\code\utilities\hypergraph\hypergraph\hypergraph.h(68) : see declaration of 'hypergraph::add_edge' definition 'hypergraph::node_iter hypergraph::add_edge(void)' existing declarations 'std::set::node*,ptr_cmp::node,P>,A>::const_iterator hypergraph::add_edge(void)' for this class: template<class T, class P> struct ptr_cmp : public std::binary_function<T, T, bool> { P p_; ptr_cmp(P p=P()) :p_(p) {} bool operator()(const T* l, const T* r) const { return p_(*l, *r);} }; template<class T, class P = std::less<T>, class A=std::allocator<T> > class hypergraph { typedef A sub_allocator; public: class node; class edge; typedef std::set<edge*, ptr_cmp<edge, std::less<edge> >, sub_allocator> edgeset; typedef std::set<node*, ptr_cmp<node, P>, sub_allocator> nodeset; typedef typename std::set<edge*, ptr_cmp<edge, std::less<edge> >, sub_allocator>::const_iterator edgeiter; typedef typename std::set<node*, ptr_cmp<node, P>, sub_allocator>::const_iterator nodeiter; class node { /*SNIP*/}; class edge { /*SNIP*/}; hypergraph(P pred=P(), A alloc=A()); nodeiter add_node(); /* beginning of 8 with the error */ nodeiter add_node(const T& rhs); nodeiter add_node(T&& rhs); nodeiter add_edge(); nodeiter erase(nodeiter iter); nodeiter erase(node* iter); nodeiter erase(edgeiter iter); nodeiter erase(edge* iter); /* end of 8 with the error */ const nodeset& nodes() const; const edgeset& edges() const; A get_allocator() const; protected: hypergraph(const hypergraph& rhs); hypergraph& operator=(const hypergraph& rhs); A a_; nodeset nodes_; edgeset edges_; unsigned int edgecount_; }; And this function definition: template<class T, class P, class A> typename hypergraph<T,P,A>::node_iter hypergraph<T,P,A>::add_edge() { std::unique_ptr<edge> ptr = new edge(edgecount_++, sub_allocator(a_)); std::pair<edgeiter, bool> r = edges_.insert(ptr); ptr.release(); return r.first; } I'm sure it's a stupid thing, but I can't figure out why MSVC10 can't match that prototype to that function. And I think this code has an issue with const-ness, caused by containers of pointers, but I'll address that in a separate question.
Return value: typename hypergraph<T,P,A>::node_iter should be: typename hypergraph<T,P,A>::nodeiter