Boost graph Library using Bundled properties - c++11

I am new to BGL and trying to setup a simple shortest path finding program using BGL where undirected graph is defined as a adjacency List with custom defined EdgeProperty and VertexProperty. I am getting compile time error which I attribute to my insufficient skills in templates and Boost.
The code is as follows:
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/directed_graph.hpp>
#include <boost/graph/graph_traits.hpp>
#include <boost/graph/dijkstra_shortest_paths.hpp>
#include<iostream>
#include <map>
using namespace std;
using namespace boost;
enum Node_type
{
STAIR,
LEVEL,
LIFT,
OTHER,
UNKNOWN
};
class VertexProperty
{
public:
VertexProperty(){ id=-1; type=Node_type::UNKNOWN, level_id=254, stair_id=254;}
std::string toString()
{
std::string str;
str = "id "+to_string(id)+" type "+to_string(type)+" level "+to_string(level_id)+" stair_id "+to_string(stair_id);
return str;
}
int getVertexID() {return id;}
int id;
Node_type type;
int level_id;
int stair_id;
};
class EdgeProperty
{
public:
EdgeProperty(){id=-1, weight=0;}
EdgeProperty(int i, double wt){ id=i; weight=wt; }
double getWeigth(){ return weight;}
int getID() {return id;}
std::string toString()
{
std::string str;
str = "id "+to_string(id)+" weight="+to_string(weight);
return str;
}
int id;
double weight;
};
typedef boost::property<boost::edge_weight_t, double> EdgeWeigthProperty;
typedef boost::adjacency_list<boost::vecS, boost::vecS, boost::undirectedS,VertexProperty, EdgeProperty> UndirectedGraph;
typedef boost::graph_traits<UndirectedGraph>::edge_iterator edge_iterator;
typedef boost::graph_traits<UndirectedGraph>::vertex_iterator vertex_iterator;
typedef boost::directed_graph <boost::no_property, EdgeWeigthProperty> DirectedGraph;
typedef boost::graph_traits<UndirectedGraph>::vertex_descriptor vertex_descriptor;
typedef boost::graph_traits<UndirectedGraph>::edge_descriptor edge_descriptor;
class A
{
public:
A();
void undirected_graph_creation();
void directed_graph_creation();
void showEdges();
void showVertices();
void run_dijkastra();
UndirectedGraph g;
DirectedGraph dg;
map<int, vertex_descriptor> map_id_vertex_desc;
map<int, edge_descriptor> map_id_edge_desc;
map<int, Node_type> map_node_id_type;
};
A::A()
{
}
void A::undirected_graph_creation()
{
UndirectedGraph::vertex_descriptor v0= boost::add_vertex(g);
map_id_vertex_desc.emplace(0,v0);
g[v0].id=0;
g[v0].type=Node_type::LEVEL;
UndirectedGraph::vertex_descriptor v1= boost::add_vertex(g);
g[v1].id=1;
g[v1].type=Node_type::STAIR;
map_id_vertex_desc.emplace(1,v1);
UndirectedGraph::vertex_descriptor v2= boost::add_vertex(g);
map_id_vertex_desc.emplace(2,v2);
g[v2].id=2;
g[v2].type=Node_type::STAIR;
UndirectedGraph::vertex_descriptor v3= boost::add_vertex(g);
map_id_vertex_desc.emplace(3,v3);
g[v3].id=3;
g[v3].type=Node_type::STAIR;
/*EdgeWeigthProperty w8(8);
EdgeWeigthProperty w18(18);
EdgeWeigthProperty w20(20);
EdgeWeigthProperty w2(2);
EdgeWeigthProperty w7(7);*/
EdgeProperty w8(1,8);
EdgeProperty w18(2,18);
EdgeProperty w20(3,20);
EdgeProperty w2(4,2);
EdgeProperty w7(5,7);
boost::add_edge(v0,v1,w8,g);
boost::add_edge(v0,v3,w18,g);
boost::add_edge(v1,v2,w20,g);
boost::add_edge(v2,v3,w2,g);
boost::add_edge(v1,v3,w7,g);
}
void A::showEdges()
{
std::pair<edge_iterator,edge_iterator> ei=edges(g);
std::cout<<" number of edges "<<num_edges(g)<<endl;
std::cout<<" Edge list ";
for(edge_iterator it= ei.first; it!=ei.second; ++it)
cout<<*it<<" "<<g[*it].toString()<<endl;
}
void A::showVertices()
{
std::pair<vertex_iterator, vertex_iterator> vi= boost::vertices(g);
std::cout<<" Number of vertices are "<<endl;
for( vertex_iterator i = vi.first; i!=vi.second; ++i)
{
cout<<*i<<" id="<<g[*i].toString()<<" type="<<g[*i].type<<endl;
}
}
void A::run_dijkastra()
{
std::vector<vertex_descriptor> predecessors(boost::num_vertices(g));
std::vector<EdgeProperty> distances(boost::num_vertices(g));
std::pair<vertex_iterator,vertex_iterator> vi=boost::vertices(g);
vertex_descriptor first_vertex_descriptor = *(vi.first);
vertex_descriptor start_vertex = boost::vertex(0,g);
// boost::dijkstra_shortest_paths(g, first_vertex_descriptor,
// boost::weight_map(get(&EdgeProperty::weight,g))
// .distance_map(boost::make_iterator_property_map(distances.begin(), get(boost::vertex_index, g))) );
dijkstra_shortest_paths(g, first_vertex_descriptor,
predecessor_map(make_iterator_property_map(predecessors.begin(), get(vertex_index,g))).distance_map(make_iterator_property_map(distances.begin(), get(boost::vertex_index,g))).weight_map(get(&EdgeProperty::weight,g));
/*
std::cout << "distances and parents:" << std::endl;
graph_traits < UndirectedGraph >::vertex_iterator vi1, vend1;
for (boost::tie(vi1, vend1) = vertices(g); vi1 != vend1; ++vi1) {
std::cout << "distance(" << g[*vi1].id << ") = " << distances[*vi1].toString() << ", ";
std::cout << "parent(" << g[*vi1].id << ") = " << g[predecessors[*vi1]].id << std::
endl;
}*/
}
void A::directed_graph_creation()
{
//todo
}
int main()
{
A a_graph;
a_graph.undirected_graph_creation();
a_graph.showEdges();
a_graph.showVertices();;
a_graph.run_dijkastra();
return 0;
}
Error is unknown conversion from double to EdgeProperty. It appear that I have mistake in syntax of call to dijkstra_shortest_paths functions.
I will also like to replace data member of EdgeProperty with a function.
Other query I have is about maintaining an index to nodes via vertex descriptor. At present, I am using VertexProperty::id do maintain a dictionary to object of VertexProperty type. Do Boost maintains internally any dictionary which I can use of.
I am using gcc5.4 version compiler on Ubuntu 16.04
Thank you
Nitin

#llonesmiz was right on the mark. Here's a general cleanup of the code and a live demo.
I also used make_transform_value_property_map to use getWeight() and made all data members private.
NOTE I suspect that the std::map instances aren't really useful anymore now that you use bundled properties (?). In any case, you could drop some code if you don't need them any more: Shorter Demo
NOTE You might not know about print_graph. Even Shorter, slightly abbreviated output
Live On Coliru
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/dijkstra_shortest_paths.hpp>
#include <boost/property_map/transform_value_property_map.hpp>
#include <iostream>
#include <map>
enum class Node_type { STAIR, LEVEL, LIFT, OTHER, UNKNOWN };
static std::ostream& operator<<(std::ostream& os, Node_type type) {
switch(type) {
case Node_type::STAIR: return os << "STAIR";
case Node_type::LEVEL: return os << "LEVEL";
case Node_type::LIFT: return os << "LIFT";
case Node_type::OTHER: return os << "OTHER";
default:
case Node_type::UNKNOWN: return os << "UNKNOWN";
}
}
class VertexProperty {
public:
VertexProperty(int id = -1, Node_type type = Node_type::UNKNOWN, int level_id=254, int stair_id=254)
: id(id), type(type), level_id(level_id), stair_id(stair_id) { }
std::string toString() const {
std::ostringstream oss;
oss << *this;
return oss.str();
}
int getVertexID() { return id; }
private:
friend std::ostream& operator<<(std::ostream& os, VertexProperty const& v) {
return os << "id " << v.id << " type " << v.type << " level " << v.level_id << " stair_id " << v.stair_id;
}
int id;
Node_type type;
int level_id;
int stair_id;
};
class EdgeProperty {
public:
EdgeProperty(int i = -1, double wt = 0) : id(i), weight(wt) {
id = i;
weight = wt;
}
double getWeight() { return weight; }
int getID() { return id; }
std::string toString() const {
std::ostringstream oss;
oss << *this;
return oss.str();
}
private:
friend std::ostream& operator<<(std::ostream& os, EdgeProperty const& e) {
return os << "id " << e.id << " weight=" << std::fixed << e.weight;
}
int id;
double weight;
};
class A {
public:
void undirected_graph_creation();
void showEdges();
void showVertices();
void runDijstra();
private:
typedef boost::adjacency_list<boost::vecS, boost::vecS, boost::undirectedS, VertexProperty, EdgeProperty> UndirectedGraph;
UndirectedGraph g;
using edge_iterator = UndirectedGraph::edge_iterator;
using vertex_iterator = UndirectedGraph::vertex_iterator;
using vertex_descriptor = UndirectedGraph::vertex_descriptor;
using edge_descriptor = UndirectedGraph::edge_descriptor;
std::map<int, vertex_descriptor> map_id_vertex_desc;
std::map<int, edge_descriptor> map_id_edge_desc;
std::map<int, Node_type> map_node_id_type;
};
void A::undirected_graph_creation() {
auto add_vertex = [this](int id, Node_type type) {
// TODO: these maps are probably not required anymore
map_node_id_type[id] = type;
vertex_descriptor vd = boost::add_vertex(VertexProperty{id, type}, g);
return map_id_vertex_desc[id] = vd;
};
auto v0 = add_vertex(0, Node_type::LEVEL);
auto v1 = add_vertex(1, Node_type::STAIR);
auto v2 = add_vertex(2, Node_type::STAIR);
auto v3 = add_vertex(3, Node_type::STAIR);
auto add_edge = [this](vertex_descriptor u, vertex_descriptor v, EdgeProperty prop) {
auto ins = boost::add_edge(u, v, prop, g);
if (ins.second)
map_id_edge_desc[prop.getID()] = ins.first;
return ins.first;
};
add_edge(v0, v1, {1, 8});
add_edge(v0, v3, {2, 18});
add_edge(v1, v2, {3, 20});
add_edge(v2, v3, {4, 2});
add_edge(v1, v3, {5, 7});
}
void A::showEdges() {
for (auto e : boost::make_iterator_range(boost::edges(g)))
std::cout << "Edge " << e << " " << g[e] << "\n";
}
void A::showVertices() {
for (auto v : boost::make_iterator_range(boost::vertices(g)))
std::cout << "Vertex " << v << " " << g[v] << "\n";
}
void A::runDijstra() {
std::vector<vertex_descriptor> predecessors(num_vertices(g));
std::vector<double> distances(num_vertices(g));
vertex_descriptor start = *(vertices(g).first);
auto v_index = get(boost::vertex_index, g);
auto weight = boost::make_transform_value_property_map(std::mem_fn(&EdgeProperty::getWeight), get(boost::edge_bundle, g));
dijkstra_shortest_paths(
g, start,
predecessor_map(make_iterator_property_map(predecessors.begin(), v_index))
.distance_map(make_iterator_property_map(distances.begin(), v_index))
.weight_map(weight));
std::cout << "distances and parents:\n";
for (auto v : boost::make_iterator_range(boost::vertices(g))) {
auto id = g[v].getVertexID();
std::cout << "distance(" << id << ") = " << distances[v] << ", ";
std::cout << "parent(" << id << ") = " << g[predecessors[v]] << "\n";
}
}
int main() {
A a;
a.undirected_graph_creation();
a.showEdges();
a.showVertices();
a.runDijstra();
}
Prints:
Edge (0,1) id 1 weight=8.000000
Edge (0,3) id 2 weight=18.000000
Edge (1,2) id 3 weight=20.000000
Edge (2,3) id 4 weight=2.000000
Edge (1,3) id 5 weight=7.000000
Vertex 0 id 0 type LEVEL level 254 stair_id 254
Vertex 1 id 1 type STAIR level 254 stair_id 254
Vertex 2 id 2 type STAIR level 254 stair_id 254
Vertex 3 id 3 type STAIR level 254 stair_id 254
distances and parents:
distance(0) = 0.000000, parent(0) = id 0 type LEVEL level 254 stair_id 254
distance(1) = 8.000000, parent(1) = id 0 type LEVEL level 254 stair_id 254
distance(2) = 17.000000, parent(2) = id 3 type STAIR level 254 stair_id 254
distance(3) = 15.000000, parent(3) = id 1 type STAIR level 254 stair_id 254

Related

terminate called after throwing an instance of 'std::logic_error' what(): basic_string::_M_construct null not valid Aborted (core dumped)

#include <iostream>
#include <vector>
using namespace std;
class House {
public:
//default constructor
House(string = "no-style", double = 0.0);
//destructor
~House();
//set the style
void setStyle(string style);
//set the cost
void setCost(double cost);
//get the style
string getStyle() const;
//get the cost
double getCost() const;
//overloaded operator: = (assignment)
House& operator = (const House &other);
//overloaded operator: == (comparison)
bool operator == (const House &other);
//overloaded operator: = (assignment)
friend ostream& operator<<(ostream& out, const House& other);
private:
string* pStyle;
double* pCost;
};
//default constructor
House::House(string style, double cost)
{
pStyle = new string{style};
pCost = new double{cost};
}
//destructor
House::~House()
{
if(pStyle != nullptr)
delete pStyle;
if(pCost != nullptr)
delete pCost;
}
//set the style
void House::setStyle(string style)
{
*pStyle = style;
}
//set the cost
void House::setCost(double cost)
{
*pCost = cost;
}
//get the style
string House::getStyle() const
{
return *pStyle;
}
//get the cost
double House::getCost() const
{
return *pCost;
}
//overloaded operator: = (assignment)
House& House::operator = (const House &other)
{
if(pStyle != nullptr)
delete pStyle;
if(pCost != nullptr)
delete pCost;
pStyle = new string{*other.pStyle};
pCost = new double{*other.pCost};
return *this;
}
//overloaded operator: == (comparison)
bool House::operator == (const House &other)
{
return (*pStyle == *other.pStyle && *pCost == *other.pCost);
}
ostream& operator<<(ostream& out, const House& other)
{
out<<"House " << other.getStyle() <<" costs:" << other.getCost()<<" dollars."<<endl;
return out;
}
void showHouses(vector<House>& houses, int houseCount) {
cout << endl << "Here are your houses: " << endl;
for(int i=0; i<houseCount; i++)
{
cout << houses.at(i);
}
}
int main() {
vector<House> houses;
House currentHouse;
int houseCount = 0; // number of houses
string style;
double cost;
int num;
cout << "Here is the currentHouse: " << currentHouse << endl;
do{
cout<<"How many houses do you want to build? (must be at least 3) ";
cin >> houseCount;
}while(houseCount<3);
cout << endl;
for(int i=0; i<houseCount; i++)
{
cout<<"What is the style for house "<<i+1<<"? ";
cin >> style;
cout<<"What is the cost for house "<<i+1<<"? ";
cin >> cost;
currentHouse.setStyle(style);
currentHouse.setCost(cost);
houses.push_back(currentHouse);
cout<<houses.at(i);
}
showHouses(houses, houseCount);
cout << endl << "Which house do you want to change? ";
cin >>num;
cout<<"What is the new style? ";
cin >> style;
cout<<"What is the new cost? ";
cin >> cost;
currentHouse = House(style, cost);
if(houses.at(num) == currentHouse)
cout<<"The new house is the same as the old house."<<endl;
else
cout<<"The houses are different."<<endl;
cout << "Copying new house to the old house ..." << endl;
houses.at(num) = currentHouse;
showHouses(houses, houseCount);
return 0;
}
The code is not working when put value of houseCount greater than 1. I have received the following error message while execute the code:
Here is the currentHouse: House no-style costs:0 dollars.
How many houses do you want to build? (must be at least 3) 3
What is the style for house 1? e
What is the cost for house 1? 2
What is the style for house 2? f
What is the cost for house 2? 5
terminate called after throwing an instance of 'std::logic_error'
what(): basic_string::_M_construct null not valid
Aborted (core dumped)

Begin was not declared in the scope for Generic Array Template Class

#include <iostream>
#include <string>
template <typename T, int N>
class Array {
int size{N};
T values{N};
friend std::ostream &operator<<(std::ostream &os, const Array<T, N> &arr) {
os << "[";
for (const auto &val: arr.values)
os << val << " ";
os << "]" << std::endl;
return os;
}
public:
Array() = default;
Array(T value_added) {
for(auto &value: values)
value = value_added;
}
void fill(T data_value) {
for (auto &value: values)
value = data_value;
}
int get_size() const {
return size;
}
T &operator[](int index) {
return values[index];
}
};
int main () {
Array<int,2> nums;
std::cout << "The size of nums is: " << nums.get_size() << std::endl;
std::cout << nums << std::endl;
}
I am trying to create a generic array template class but I get the error Begin was not declared in the scope and it says it is due to the for (const auto &val: arr.values) loop.

Boost asio thread_pool join does not wait for tasks to be finished

Consider the functions
#include <iostream>
#include <boost/bind.hpp>
#include <boost/asio.hpp>
void foo(const uint64_t begin, uint64_t *result)
{
uint64_t prev[] = {begin, 0};
for (uint64_t i = 0; i < 1000000000; ++i)
{
const auto tmp = (prev[0] + prev[1]) % 1000;
prev[1] = prev[0];
prev[0] = tmp;
}
*result = prev[0];
}
void batch(boost::asio::thread_pool &pool, const uint64_t a[])
{
uint64_t r[] = {0, 0};
boost::asio::post(pool, boost::bind(foo, a[0], &r[0]));
boost::asio::post(pool, boost::bind(foo, a[1], &r[1]));
pool.join();
std::cerr << "foo(" << a[0] << "): " << r[0] << " foo(" << a[1] << "): " << r[1] << std::endl;
}
where foo is a simple "pure" function that performs a calculation on begin and writes the result to the pointer *result.
This function gets called with different inputs from batch. Here dispatching each call to another CPU core might be beneficial.
Now assume the batch function gets called several 10 000 times. Therefore a thread pool would be nice which is shared between all the sequential batch calls.
Trying this with (for the sake of simplicity only 3 calls)
int main(int argn, char **)
{
boost::asio::thread_pool pool(2);
const uint64_t a[] = {2, 4};
batch(pool, a);
const uint64_t b[] = {3, 5};
batch(pool, b);
const uint64_t c[] = {7, 9};
batch(pool, c);
}
leads to the result
foo(2): 2 foo(4): 4
foo(3): 0 foo(5): 0
foo(7): 0 foo(9): 0
Where all three lines appear at the same time, while the computation of foo takes ~3s.
I assume that only the first join really waits for the pool to complete all jobs.
The others have invalid results. (The not initialized values)
What is the best practice here to reuse the thread pool?
The best practice is not to reuse the pool (what would be the use of pooling, if you keep creating new pools?).
If you want to be sure you "time" the batches together, I'd suggest using when_all on futures:
Live On Coliru
#define BOOST_THREAD_PROVIDES_FUTURE_WHEN_ALL_WHEN_ANY
#include <iostream>
#include <boost/bind.hpp>
#include <boost/asio.hpp>
#include <boost/thread.hpp>
uint64_t foo(uint64_t begin) {
uint64_t prev[] = {begin, 0};
for (uint64_t i = 0; i < 1000000000; ++i) {
const auto tmp = (prev[0] + prev[1]) % 1000;
prev[1] = prev[0];
prev[0] = tmp;
}
return prev[0];
}
void batch(boost::asio::thread_pool &pool, const uint64_t a[2])
{
using T = boost::packaged_task<uint64_t>;
T tasks[] {
T(boost::bind(foo, a[0])),
T(boost::bind(foo, a[1])),
};
auto all = boost::when_all(
tasks[0].get_future(),
tasks[1].get_future());
for (auto& t : tasks)
post(pool, std::move(t));
auto [r0, r1] = all.get();
std::cerr << "foo(" << a[0] << "): " << r0.get() << " foo(" << a[1] << "): " << r1.get() << std::endl;
}
int main() {
boost::asio::thread_pool pool(2);
const uint64_t a[] = {2, 4};
batch(pool, a);
const uint64_t b[] = {3, 5};
batch(pool, b);
const uint64_t c[] = {7, 9};
batch(pool, c);
}
Prints
foo(2): 2 foo(4): 4
foo(3): 503 foo(5): 505
foo(7): 507 foo(9): 509
I would consider
generalizing
message queuing
Generalized
Make it somewhat more flexible by not hardcoding batch sizes. After all, the pool size is already fixed, we don't need to "make sure batches fit" or something:
Live On Coliru
#define BOOST_THREAD_PROVIDES_FUTURE_WHEN_ALL_WHEN_ANY
#include <iostream>
#include <boost/bind.hpp>
#include <boost/asio.hpp>
#include <boost/thread.hpp>
#include <boost/thread/future.hpp>
struct Result { uint64_t begin, result; };
Result foo(uint64_t begin) {
uint64_t prev[] = {begin, 0};
for (uint64_t i = 0; i < 1000000000; ++i) {
const auto tmp = (prev[0] + prev[1]) % 1000;
prev[1] = prev[0];
prev[0] = tmp;
}
return { begin, prev[0] };
}
void batch(boost::asio::thread_pool &pool, std::vector<uint64_t> const a)
{
using T = boost::packaged_task<Result>;
std::vector<T> tasks;
tasks.reserve(a.size());
for(auto begin : a)
tasks.emplace_back(boost::bind(foo, begin));
std::vector<boost::unique_future<T::result_type> > futures;
for (auto& t : tasks) {
futures.push_back(t.get_future());
post(pool, std::move(t));
}
for (auto& fut : boost::when_all(futures.begin(), futures.end()).get()) {
auto r = fut.get();
std::cerr << "foo(" << r.begin << "): " << r.result << " ";
}
std::cout << std::endl;
}
int main() {
boost::asio::thread_pool pool(2);
batch(pool, {2});
batch(pool, {4, 3, 5});
batch(pool, {7, 9});
}
Prints
foo(2): 2
foo(4): 4 foo(3): 503 foo(5): 505
foo(7): 507 foo(9): 509
Generalized2: Variadics Simplify
Contrary to popular believe (and honestly, what usually happens) this time we can leverage variadics to get rid of all the intermediate vectors (every single one of them):
Live On Coliru
void batch(boost::asio::thread_pool &pool, T... a)
{
auto launch = [&pool](uint64_t begin) {
boost::packaged_task<Result> pt(boost::bind(foo, begin));
auto fut = pt.get_future();
post(pool, std::move(pt));
return fut;
};
for (auto& r : {launch(a).get()...}) {
std::cerr << "foo(" << r.begin << "): " << r.result << " ";
}
std::cout << std::endl;
}
If you insist on outputting the results in time, you can still add when_all into the mix (requiring a bit more heroics to unpack the tuple):
Live On Coliru
template <typename...T>
void batch(boost::asio::thread_pool &pool, T... a)
{
auto launch = [&pool](uint64_t begin) {
boost::packaged_task<Result> pt(boost::bind(foo, begin));
auto fut = pt.get_future();
post(pool, std::move(pt));
return fut;
};
std::apply([](auto&&... rfut) {
Result results[] {rfut.get()...};
for (auto& r : results) {
std::cerr << "foo(" << r.begin << "): " << r.result << " ";
}
}, boost::when_all(launch(a)...).get());
std::cout << std::endl;
}
Both still print the same result
Message Queuing
This is very natural to boost, and sort of skips most complexity. If you also want to report per batched group, you'd have to coordinate:
Live On Coliru
#include <iostream>
#include <boost/asio.hpp>
#include <memory>
struct Result { uint64_t begin, result; };
Result foo(uint64_t begin) {
uint64_t prev[] = {begin, 0};
for (uint64_t i = 0; i < 1000000000; ++i) {
const auto tmp = (prev[0] + prev[1]) % 1000;
prev[1] = prev[0];
prev[0] = tmp;
}
return { begin, prev[0] };
}
using Group = std::shared_ptr<size_t>;
void batch(boost::asio::thread_pool &pool, std::vector<uint64_t> begins) {
auto group = std::make_shared<std::vector<Result> >(begins.size());
for (size_t i=0; i < begins.size(); ++i) {
post(pool, [i,begin=begins.at(i),group] {
(*group)[i] = foo(begin);
if (group.unique()) {
for (auto& r : *group) {
std::cout << "foo(" << r.begin << "): " << r.result << " ";
std::cout << std::endl;
}
}
});
}
}
int main() {
boost::asio::thread_pool pool(2);
batch(pool, {2});
batch(pool, {4, 3, 5});
batch(pool, {7, 9});
pool.join();
}
Note this is having concurrent access to group, which is safe due to the limitations on element accesses.
Prints:
foo(2): 2
foo(4): 4 foo(3): 503 foo(5): 505
foo(7): 507 foo(9): 509
I just ran into this advanced executor example which is hidden from the documentation:
I realized just now that Asio comes with a fork_executor example which does exactly this: you can "group" tasks and join the executor (which represents that group) instead of the pool. I've missed this for the longest time since none of the executor examples are listed in the HTML documentation – sehe 21 mins ago
So without further ado, here's that sample applied to your question:
Live On Coliru
#define BOOST_BIND_NO_PLACEHOLDERS
#include <boost/asio/thread_pool.hpp>
#include <boost/asio/ts/executor.hpp>
#include <condition_variable>
#include <memory>
#include <mutex>
#include <queue>
#include <thread>
// A fixed-size thread pool used to implement fork/join semantics. Functions
// are scheduled using a simple FIFO queue. Implementing work stealing, or
// using a queue based on atomic operations, are left as tasks for the reader.
class fork_join_pool : public boost::asio::execution_context {
public:
// The constructor starts a thread pool with the specified number of
// threads. Note that the thread_count is not a fixed limit on the pool's
// concurrency. Additional threads may temporarily be added to the pool if
// they join a fork_executor.
explicit fork_join_pool(std::size_t thread_count = std::thread::hardware_concurrency()*2)
: use_count_(1), threads_(thread_count)
{
try {
// Ask each thread in the pool to dequeue and execute functions
// until it is time to shut down, i.e. the use count is zero.
for (thread_count_ = 0; thread_count_ < thread_count; ++thread_count_) {
boost::asio::dispatch(threads_, [&] {
std::unique_lock<std::mutex> lock(mutex_);
while (use_count_ > 0)
if (!execute_next(lock))
condition_.wait(lock);
});
}
} catch (...) {
stop_threads();
threads_.join();
throw;
}
}
// The destructor waits for the pool to finish executing functions.
~fork_join_pool() {
stop_threads();
threads_.join();
}
private:
friend class fork_executor;
// The base for all functions that are queued in the pool.
struct function_base {
std::shared_ptr<std::size_t> work_count_;
void (*execute_)(std::shared_ptr<function_base>& p);
};
// Execute the next function from the queue, if any. Returns true if a
// function was executed, and false if the queue was empty.
bool execute_next(std::unique_lock<std::mutex>& lock) {
if (queue_.empty())
return false;
auto p(queue_.front());
queue_.pop();
lock.unlock();
execute(lock, p);
return true;
}
// Execute a function and decrement the outstanding work.
void execute(std::unique_lock<std::mutex>& lock,
std::shared_ptr<function_base>& p) {
std::shared_ptr<std::size_t> work_count(std::move(p->work_count_));
try {
p->execute_(p);
lock.lock();
do_work_finished(work_count);
} catch (...) {
lock.lock();
do_work_finished(work_count);
throw;
}
}
// Increment outstanding work.
void
do_work_started(const std::shared_ptr<std::size_t>& work_count) noexcept {
if (++(*work_count) == 1)
++use_count_;
}
// Decrement outstanding work. Notify waiting threads if we run out.
void
do_work_finished(const std::shared_ptr<std::size_t>& work_count) noexcept {
if (--(*work_count) == 0) {
--use_count_;
condition_.notify_all();
}
}
// Dispatch a function, executing it immediately if the queue is already
// loaded. Otherwise adds the function to the queue and wakes a thread.
void do_dispatch(std::shared_ptr<function_base> p,
const std::shared_ptr<std::size_t>& work_count) {
std::unique_lock<std::mutex> lock(mutex_);
if (queue_.size() > thread_count_ * 16) {
do_work_started(work_count);
lock.unlock();
execute(lock, p);
} else {
queue_.push(p);
do_work_started(work_count);
condition_.notify_one();
}
}
// Add a function to the queue and wake a thread.
void do_post(std::shared_ptr<function_base> p,
const std::shared_ptr<std::size_t>& work_count) {
std::lock_guard<std::mutex> lock(mutex_);
queue_.push(p);
do_work_started(work_count);
condition_.notify_one();
}
// Ask all threads to shut down.
void stop_threads() {
std::lock_guard<std::mutex> lock(mutex_);
--use_count_;
condition_.notify_all();
}
std::mutex mutex_;
std::condition_variable condition_;
std::queue<std::shared_ptr<function_base>> queue_;
std::size_t use_count_;
std::size_t thread_count_;
boost::asio::thread_pool threads_;
};
// A class that satisfies the Executor requirements. Every function or piece of
// work associated with a fork_executor is part of a single, joinable group.
class fork_executor {
public:
fork_executor(fork_join_pool& ctx)
: context_(ctx), work_count_(std::make_shared<std::size_t>(0)) {}
fork_join_pool& context() const noexcept { return context_; }
void on_work_started() const noexcept {
std::lock_guard<std::mutex> lock(context_.mutex_);
context_.do_work_started(work_count_);
}
void on_work_finished() const noexcept {
std::lock_guard<std::mutex> lock(context_.mutex_);
context_.do_work_finished(work_count_);
}
template <class Func, class Alloc>
void dispatch(Func&& f, const Alloc& a) const {
auto p(std::allocate_shared<exFun<Func>>(
typename std::allocator_traits<Alloc>::template rebind_alloc<char>(a),
std::move(f), work_count_));
context_.do_dispatch(p, work_count_);
}
template <class Func, class Alloc> void post(Func f, const Alloc& a) const {
auto p(std::allocate_shared<exFun<Func>>(
typename std::allocator_traits<Alloc>::template rebind_alloc<char>(a),
std::move(f), work_count_));
context_.do_post(p, work_count_);
}
template <class Func, class Alloc>
void defer(Func&& f, const Alloc& a) const {
post(std::forward<Func>(f), a);
}
friend bool operator==(const fork_executor& a, const fork_executor& b) noexcept {
return a.work_count_ == b.work_count_;
}
friend bool operator!=(const fork_executor& a, const fork_executor& b) noexcept {
return a.work_count_ != b.work_count_;
}
// Block until all work associated with the executor is complete. While it
// is waiting, the thread may be borrowed to execute functions from the
// queue.
void join() const {
std::unique_lock<std::mutex> lock(context_.mutex_);
while (*work_count_ > 0)
if (!context_.execute_next(lock))
context_.condition_.wait(lock);
}
private:
template <class Func> struct exFun : fork_join_pool::function_base {
explicit exFun(Func f, const std::shared_ptr<std::size_t>& w)
: function_(std::move(f)) {
work_count_ = w;
execute_ = [](std::shared_ptr<fork_join_pool::function_base>& p) {
Func tmp(std::move(static_cast<exFun*>(p.get())->function_));
p.reset();
tmp();
};
}
Func function_;
};
fork_join_pool& context_;
std::shared_ptr<std::size_t> work_count_;
};
// Helper class to automatically join a fork_executor when exiting a scope.
class join_guard {
public:
explicit join_guard(const fork_executor& ex) : ex_(ex) {}
join_guard(const join_guard&) = delete;
join_guard(join_guard&&) = delete;
~join_guard() { ex_.join(); }
private:
fork_executor ex_;
};
//------------------------------------------------------------------------------
#include <algorithm>
#include <iostream>
#include <random>
#include <vector>
#include <boost/bind.hpp>
static void foo(const uint64_t begin, uint64_t *result)
{
uint64_t prev[] = {begin, 0};
for (uint64_t i = 0; i < 1000000000; ++i) {
const auto tmp = (prev[0] + prev[1]) % 1000;
prev[1] = prev[0];
prev[0] = tmp;
}
*result = prev[0];
}
void batch(fork_join_pool &pool, const uint64_t (&a)[2])
{
uint64_t r[] = {0, 0};
{
fork_executor fork(pool);
join_guard join(fork);
boost::asio::post(fork, boost::bind(foo, a[0], &r[0]));
boost::asio::post(fork, boost::bind(foo, a[1], &r[1]));
// fork.join(); // or let join_guard destructor run
}
std::cerr << "foo(" << a[0] << "): " << r[0] << " foo(" << a[1] << "): " << r[1] << std::endl;
}
int main() {
fork_join_pool pool;
batch(pool, {2, 4});
batch(pool, {3, 5});
batch(pool, {7, 9});
}
Prints:
foo(2): 2 foo(4): 4
foo(3): 503 foo(5): 505
foo(7): 507 foo(9): 509
Things to note:
executors can overlap/nest: you can use several joinable fork_executors on a single fork_join_pool and they will join the distinct groups of tasks for each executor
You can get that sense easily when looking at the library example (which does a recursive divide-and-conquer merge sort).
I had a similar problem and ended up using latches. In this case the code would would be (I also switched from bind to lambdas):
void batch(boost::asio::thread_pool &pool, const uint64_t a[])
{
uint64_t r[] = {0, 0};
boost::latch latch(2);
boost::asio::post(pool, [&](){ foo(a[0], &r[0]); latch.count_down();});
boost::asio::post(pool, [&](){ foo(a[1], &r[1]); latch.count_down();});
latch.wait();
std::cerr << "foo(" << a[0] << "): " << r[0] << " foo(" << a[1] << "): " << r[1] << std::endl;
}
https://godbolt.org/z/oceP6jjs7

How to keep all properties of graph read by boost::read_graphviz?

Suppose you want to read in a .dot graph into boost where there could be properties you do not recognize. It is easy to ignore them (by passing ignore_other_properties to the dynamic_properties constructor) but what if you wanted all the properties added to dynamic_properties instead?
The below code demonstrates the problem. The handle_custom_properties is a copy of ignore_other_properties and the code will compile / run, reporting "3 vertices, 2 edges." What needs to be added to handle_custom_properties so that, on return, dp will contain a property "label" and node A will have value "x" for the label property?
#include <iostream>
#include <string>
#include <boost/graph/graphviz.hpp>
#include <boost/graph/adjacency_list.hpp>
#include <boost/exception/exception.hpp>
#include <boost/exception/diagnostic_information.hpp>
struct vertex_p {
std::string node_id;
};
typedef boost::adjacency_list < boost::vecS, boost::vecS, boost::undirectedS, vertex_p> graph_t;
boost::shared_ptr<boost::dynamic_property_map>
handle_custom_properties(const std::string& s,
const boost::any& k,
const boost::any& v) {
// What goes in here to add dynamic property map for property "s" which key-value pair <k,v>?
// What ignore_other_properties does
return boost::shared_ptr<boost::dynamic_property_map>();
}
int main() {
std::string str(R"(graph {
A [ label="x" ]
B
C
A -- B
A -- C
}
)");
try {
graph_t g;
boost::dynamic_properties dp{handle_custom_properties};
dp.property("node_id", get(&vertex_p::node_id, g));
if (boost::read_graphviz(str, g, dp)) {
std::cout << "read_graphviz returned success" << std::endl;
std::cout << "graph stats:" << std::endl;
std::cout << " " << g.m_vertices.size() << " vertices" << std::endl;
std::cout << " " << g.m_edges.size() << " edges" << std::endl;
}
else {
std::cout << "read_graphviz returned failure" << std::endl;
}
}
catch (std::exception& e) {
std::cerr << e.what() << std::endl;
}
catch (boost::exception& e) {
std::cerr << boost::diagnostic_information(e) << std::endl;
}
}
I was not able to find an existing class to solve this but implementing a dynamic_property_map in terms of a std::map worked:
template<typename TKey, typename TValue>
class dynamic_property_map_impl : public boost::dynamic_property_map {
std::map<TKey, TValue> map_;
public:
boost::any get(const boost::any& key) override { return map_[boost::any_cast<TKey>(key)]; }
std::string get_string(const boost::any& key) override { std::ostringstream s; s << map_[boost::any_cast<TKey>(key)]; return s.str(); }
void put(const boost::any& key, const boost::any& value) override { map_[boost::any_cast<TKey>(key)] = boost::any_cast<TValue>(value); }
const std::type_info& key() const override { return typeid(TKey); }
const std::type_info& value() const override { return typeid(TValue); }
};
boost::shared_ptr<boost::dynamic_property_map>
handle_custom_properties(const std::string&,
const boost::any&,
const boost::any&) {
return boost::make_shared<dynamic_property_map_impl<unsigned, std::string>>();
}
Changing the graph and printing out the properties shows all the property maps were added:
std::string str(R"(graph {
A [ label="x", stuff="y" ]
B
C [ happy="yes" ]
A -- B
A -- C
}
)");
...
std::cout << "properties:" << std::endl;
for (const auto& p : dp) {
std::cout << " " << p.first << std::endl;
}
Outputs
read_graphviz returned success
graph stats:
3 vertices
2 edges
properties:
happy
label
node_id
stuff

how would I go about handling these return type errors? (hashmap/table)

#ifndef HASHMAP_H
#define HASHMAP_H
#include <iostream>
#include <string>
#include <vector>
using namespace std;
enum Status{open , active, deactivated };
//template <typename T>
template</*typename Key,*/ typename T>
class hashmap{
private:
class Node{
public:
const string Key;
//vector<T> values;
T value;
Status status;
Node(string key, T val) :Key(key), value(val), status(active){}
void operator =(const Node &n){
string *ptr;
ptr = (string*)(&(this->Key));
*ptr = n.Key;
//Node(n);
this->status = n.status;
this->value = n.value;
}
Node() :status(open){}
Node(const string& key) :Key(key), status(active){}
//Node(const Node &n) : value(n.val), status(n.status){}
};
//typedef map<
unsigned int hash(const string& s, int tableSize){
unsigned int h = 0;
/*each(s)*/
for(auto it : s) h = 31 * h + unsigned(it);
return h % tableSize;
}
unsigned int hash(const string& s){
return hash(s, table_size);
}
int table_size = 103;
vector<Node> table;
typedef typename vector<Node>::iterator iter;
public:
//default constructor
hashmap(){
table = vector<Node>(table_size);
}
//copy constructor
hashmap(const hashmap& x){
table = x.table;
//for (auto it = )
}
//assignment operator //has been removed
hashmap& operator=(const hashmap& x){
this->table.erase(this->table.begin(), this->table.begin() + 103);
for ( int i = 0; i < x.table_size; i++){
this->table.push_back(x.table.at(i));
}
return *this;
}
//destructor
~hashmap(){
table.clear();
}
//index operator
T& operator[](const string x){
int h = hash(x, table.size());
if (table[h].Key == x){
return (table[h].value);
}
else {
Node* n = new Node(x);
table[h] = *n;
return (table[h].value);
}
}
//Node test
void okay(const string x,int i){
Node *temp = new Node(x, i);
cout << temp->status << endl;
/*cout << table[1].status << endl;
cout << table[2].status << endl;
table.at(0) = (*temp);
cout << table[0].Key << endl;
cout << table[0].value << endl;
cout << table[3].status << endl;*/
}
int stride(int x){
return (7-x%7);
}
//find()
iter find(const string& x){
int h = hash(x);
int s = stride(h);
int t = table_size;
int z;
//for (int i = 0; i < t; i++){
for (int i = hash(x, table_size) % t; i != t; i = (i + stride(h)) % t){
z = (h + i*s) % table_size;
if (table[z].status == open) return NULL;
if (table[z].status == deactivated) continue;
if (table[z].Key == x) return &table[h];
}
return table.end();
}
//begin()
iter begin(){
return table.begin();
}
//end()
iter end(){
return table.end();
}
};
#endif // !HASHMAP_H
Everything seems to be working fine except the find function. It's suppose to probe through the vector and return values upon conditions but the problem I'm having is I get these errors about return type conflicts.
Error2error C2664: 'std::_Vector_iterator<std::_Vector_val<std::_Simple_types<hashmap::Node>>>::_Vector_iterator(const
std::_Vector_iterator<std::_Vector_val<std::_Simple_types<hashmap::Node>>> &)' : cannot convert argument 1 from 'hashmap<int>::Node *' to 'const
std::_Vector_iterator<std::_Vector_val<std::_Simple_types<hashmap<int>::Node>>> &'
Error1error C2664: 'std::_Vector_iterator<std::_Vector_val<std::_Simple_types<hashmap<int>::Node>>>::_Vector_iterator(const
std::_Vector_iterator<std::_Vector_val<std::_Simple_types<hashmap<int>::Node>>> &)' : cannot convert argument 1 from 'int' to 'const
std::_Vector_iterator<std::_Vector_val<std::_Simple_types<hashmap<int>::Node>>> &'
How can I edit the iterator to fix this?
thank you.

Resources