boost::geometry::union_ produces self intersections - c++

I have two valid polygons. When I take their union, I am getting an invalid polygon (there are self intersections). Is this a bug? I would expect that the union operation would always produce a valid polygon. I have provided an example below along with visualizations. Can anyone explain why this isn't a bug, or explain if there's a way to fix it?
#include <fstream>
#include <iostream>
#include <boost/geometry.hpp> // read_wkt
#include <boost/geometry/geometries/point_xy.hpp>
#include <boost/geometry/geometries/polygon.hpp>
#include <boost/geometry/geometries/multi_polygon.hpp>
#include <boost/geometry/algorithms/union.hpp>
using PointType = boost::geometry::model::d2::point_xy<double>;
using PolygonType = boost::geometry::model::polygon<PointType>;
using MultiPolygonType = boost::geometry::model::multi_polygon<PolygonType>;
template <typename TPolygon>
void WritePolygonsToSVG(const std::vector<TPolygon>& polygons, const std::string& filename)
{
std::ofstream svg(filename);
boost::geometry::svg_mapper<PointType> mapper(svg, 400, 400);
for(unsigned int i = 0; i < polygons.size(); ++i) {
mapper.add(polygons[i]);
mapper.map(polygons[i], "fill:rgb(255,128,0);stroke:rgb(0,0,100);stroke-width:1");
}
}
int main(int, char**)
{
/// Create two polygons
PolygonType singlePolygon1;
PolygonType singlePolygon2;
boost::geometry::read_wkt("POLYGON((-52.8018 -26.744,-57.5465 -27.9916,-62.2844 -29.2642,-63.19 -26.066,-57.564 -24.5243,-53.7273 -23.3394,-52.8018 -26.744))", singlePolygon1);
boost::geometry::read_wkt("POLYGON((-56.9695 -33.6407,-58.009 -33.0132,-58.5119 -32.8011,-59.0182 -32.6562,-59.5392 -32.5779,-60.0858 -32.5658,"
"-61.3005 -32.7384,-62.2844 -29.2642,-59.997 -28.7264,-58.0701 -28.125,-56.7078 -27.7124,-55.3109 -27.4556,-55.0955 -27.4599,"
"-54.8762 -27.503,-54.6545 -27.5806,-54.4318 -27.6887,-53.9893 -27.98,-53.5601 -28.344,-52.7879 -29.1592,-52.207 -29.8722,-56.9695 -33.6407))", singlePolygon2);
boost::geometry::correct(singlePolygon1);
boost::geometry::correct(singlePolygon2);
/// Run union and check validity (should fail check)
MultiPolygonType unionResult;
boost::geometry::union_(singlePolygon1, singlePolygon2, unionResult);
boost::geometry::validity_failure_type failure_type;
if(!boost::geometry::is_valid(unionResult, failure_type)) {
std::cout << "Result of union operation is not valid! " << failure_type << std::endl; // failure_type is 21 == failure_self_intersections
}
// Put these into the same type so that they can be passed to WritePolygonsToSVG
MultiPolygonType polygon1;
polygon1.push_back(singlePolygon1);
MultiPolygonType polygon2;
polygon2.push_back(singlePolygon2);
std::vector<MultiPolygonType> polygons = {polygon1, polygon2, unionResult};
WritePolygonsToSVG(polygons, "allPolygons.svg");
return EXIT_SUCCESS;
}
Polygon1:
Polygon2:
Both input polygons:
Union polygon (self intersections):
The actual problem that I'm running into is that if I then try to union the (invalid) output polygon with another polygon, it throws an exception:
terminate called after throwing an instance of 'boost::geometry::overlay_invalid_input_exception'
what(): Boost.Geometry Overlay invalid input exception
This expanded example demonstrates the exception:
#include <boost/geometry.hpp>
#include <boost/geometry/geometries/point_xy.hpp>
#include <boost/geometry/geometries/polygon.hpp>
#include <boost/geometry/geometries/multi_polygon.hpp>
#include <boost/geometry/algorithms/assign.hpp>
#include <boost/geometry/algorithms/intersection.hpp>
#include <boost/geometry/algorithms/union.hpp>
int main(int argc, char** argv) {
using PointType = boost::geometry::model::d2::point_xy<double>;
using PolygonType = boost::geometry::model::polygon<PointType>;
using MultiPolygonType = boost::geometry::model::multi_polygon<PolygonType>;
/// Read in two polygons
PolygonType polygon1;
PolygonType polygon2;
boost::geometry::read_wkt("POLYGON((-52.8018 -26.744,-57.5465 -27.9916,-62.2844 -29.2642,-63.19 -26.066,-57.564 -24.5243,-53.7273 -23.3394,-52.8018 -26.744))", polygon1);
boost::geometry::read_wkt("POLYGON((-56.9695 -33.6407,-58.009 -33.0132,-58.5119 -32.8011,-59.0182 -32.6562,-59.5392 -32.5779,-60.0858 -32.5658,"
"-61.3005 -32.7384,-62.2844 -29.2642,-59.997 -28.7264,-58.0701 -28.125,-56.7078 -27.7124,-55.3109 -27.4556,-55.0955 -27.4599,"
"-54.8762 -27.503,-54.6545 -27.5806,-54.4318 -27.6887,-53.9893 -27.98,-53.5601 -28.344,-52.7879 -29.1592,-52.207 -29.8722,-56.9695 -33.6407))", polygon2);
/// Run union and check validity (should fail check)
MultiPolygonType unionResult1;
boost::geometry::union_(polygon1, polygon2, unionResult1);
boost::geometry::validity_failure_type failure_type;
if(!boost::geometry::is_valid(unionResult1, failure_type)) {
std::cout << "Result of union operation is not valid!" << std::endl;
}
/// Perform second union with third polygon (should throw)
MultiPolygonType polygon3;
boost::geometry::read_wkt("MULTIPOLYGON(((-36.5181 -22.1798,-39.072 -22.991,-41.6641 -23.6833,-52.8018 -26.744,-53.7273 -23.3394,-45.7888 -21.0022,"
"-41.8671 -19.6963,-37.9783 -18.2852,-36.5181 -22.1798)),((-52.207 -29.8722,-51.6368 -30.7643,-51.1556 -31.8424,-50.7568 -33.0527,"
"-50.4336 -34.3414,-49.9875 -36.9394,-49.8512 -38.1412,-49.7639 -39.2066,-54.0482 -39.4903,-54.1623 -38.8728,-54.4115 -38.1178,"
"-55.1907 -36.4023,-56.1365 -34.758,-56.594 -34.092,-57.0005 -33.5988,-57.0016 -33.5993,-57.0036 -33.6033,-57.0033 -33.6044,"
"-57.0029 -33.6055,-57.0014 -33.6079,-56.9995 -33.6106,-56.9941 -33.6165,-56.9695 -33.6407,-52.207 -29.8722)),((-61.3005 -32.7384,"
"-65.9902 -34.1028,-70.659 -35.5553,-75.2871 -37.1219,-79.8551 -38.829,-84.6551 -40.8483,-87.0116 -41.984,-89.2911 -43.2272,-92.5791 -37.6053,"
"-89.0742 -35.6754,-85.5037 -33.9031,-78.2194 -30.6022,-74.8627 -29.2273,-71.9907 -28.2985,-69.0648 -27.5347,-63.19 -26.066,-61.3005 -32.7384)),"
"((75.3002 -38.7765,72.918 -39.2671,70.5558 -39.533,68.2188 -39.5707,65.912 -39.3766,63.6406 -38.9473,61.4095 -38.2792,59.2239 -37.3688,"
"57.089 -36.2125,55.5367 -35.1756,54.0721 -34.0078,52.7036 -32.7209,51.4398 -31.3265,50.2893 -29.8362,49.2607 -28.2616,48.3624 -26.6144,"
"47.6032 -24.9062,47.158 -23.7161,46.803 -22.6211,46.3142 -20.5856,46.0379 -18.5376,45.9485 -17.427,45.8751 -16.215,45.5849 -10.2471,"
"45.4467 -7.25562,45.2962 -4.26324,45.0667 0.0336182,44.8334 4.32845,44.3738 10.6768,42.9884 29.2827,42.3055 38.5987,41.6559 47.9296,"
"45.1275 48.1622,45.7738 38.8379,46.4466 29.5288,47.7834 10.934,48.2323 4.58677,48.587 0.296648,48.9992 -4.00063,49.299 -6.95733,"
"49.6132 -9.93119,50.1835 -15.8585,50.3639 -17.8241,50.6215 -19.6471,51.0408 -21.4515,51.7068 -23.3609,52.3326 -24.729,53.0701 -26.047,"
"53.9117 -27.3065,54.8496 -28.4987,55.8759 -29.6152,56.9829 -30.6474,58.1629 -31.5867,59.408 -32.4246,60.9346 -33.2618,62.5325 -33.9455,"
"64.1878 -34.476,65.8864 -34.8539,67.3309 -35.051,68.7878 -35.1355,70.2399 -35.1053,72.7143 -34.7658,74.0086 -34.5489,75.3002 -38.7765)))", polygon3);
MultiPolygonType unionResult2;
boost::geometry::union_(unionResult1, polygon3, unionResult2); // throws
std::cout << "Result: " << boost::geometry::wkt(unionResult2) << std::endl;
return EXIT_SUCCESS;
}

I think whatever was the bug, was fixed.
Here's the result of the extended sample on my box with GCC 5.4 and Boost 1.62.0:
Result: MULTIPOLYGON(((75.3002 -38.7765,72.918 -39.2671,70.5558 -39.533,68.2188
-39.5707,65.912 -39.3766,63.6406 -38.9473,61.4095 -38.2792,59.2239
-37.3688,57.089 -36.2125,55.5367 -35.1756,54.0721 -34.0078,52.7036
-32.7209,51.4398 -31.3265,50.2893 -29.8362,49.2607 -28.2616,48.3624
-26.6144,47.6032 -24.9062,47.158 -23.7161,46.803 -22.6211,46.3142
-20.5856,46.0379 -18.5376,45.9485 -17.427,45.8751 -16.215,45.5849
-10.2471,45.4467 -7.25562,45.2962 -4.26324,45.0667 0.0336182,44.8334
4.32845,44.3738 10.6768,42.9884 29.2827,42.3055 38.5987,41.6559 47.9296,45.1275
48.1622,45.7738 38.8379,46.4466 29.5288,47.7834 10.934,48.2323 4.58677,48.587
0.296648,48.9992 -4.00063,49.299 -6.95733,49.6132 -9.93119,50.1835
-15.8585,50.3639 -17.8241,50.6215 -19.6471,51.0408 -21.4515,51.7068
-23.3609,52.3326 -24.729,53.0701 -26.047,53.9117 -27.3065,54.8496
-28.4987,55.8759 -29.6152,56.9829 -30.6474,58.1629 -31.5867,59.408
-32.4246,60.9346 -33.2618,62.5325 -33.9455,64.1878 -34.476,65.8864
-34.8539,67.3309 -35.051,68.7878 -35.1355,70.2399 -35.1053,72.7143
-34.7658,74.0086 -34.5489,75.3002 -38.7765),(-62.2843 -29.2642,-59.997
-28.7264,-58.2365 -28.1769,-62.2843 -29.2642)))

Related

error: no match for call to '(const std::ranges::__sort_fn)

I was practicing vectors and ranges in c++ 20 was stuck at following state.
#include <iostream>
#include <vector>
#include <random>
#include <ranges>
#include <algorithm>
namespace ranges = std::ranges;
struct Model
{
double next_event_time;
};
std::vector<Model> generate_examples(int number)
{
// A uniform random number generator object
// Used as the source of randomness
std::default_random_engine generator;
// Calls () operator on generator to get uniformly-distributed integers
// then transforms the obtained values to output the disired distribution
// Will uniformly generate values between 0 ~ 1
std::uniform_real_distribution<double> distribution(0.0, 1.0);
std::vector<Model> models;
for (auto i = 0; i < number; i++)
{
models.push_back(Model{.next_event_time = distribution(generator)});
}
return models;
}
Model get_next_model(const std::vector<Model> &models)
{
ranges::sort(models | std::views::transform([](const Model &x) { return x.next_event_time; }));
return models[0];
}
int main()
{
std::vector<Model> models = generate_examples(10);
for (const auto &model : models)
std::cout << model.next_event_time << std::endl;
}
I compiled the code with g++ 10.2 and got error
error: no match for call to '(const std::ranges::__sort_fn) ~~~
ranges::sort(models | std::views::transform([](const Model &x) { return x.next_event_time; }));
Instead of std::views::transform, I also tried
lambda expression
ranges::sort(models, {}, &Model::next_event_time)
But they all produced similar no match for call to error. Why is this happening?
Your function should be as this:
Model get_next_model( std::vector<Model> models)
{
ranges::sort(models, ranges::less{}, [](const Model &x) { return x.next_event_time; });
return models[0];
}
There were two problems:
You cannot sort const object (so remove const&)
The signature of sort requires way of sorting (ranges::less) before projections. And transform has no sense here

Trying to convert a component of wxVector<m_Product*> that is a wxString to wxStringArray but the program doesn't wanna to

The idea is to convert an wxVector to a wxStringArray so i did a simple function to do it for me, this one:
wxArrayString m_ProductStruct::m_ArrayConverter(const wxVector<m_Product*>& m_Prod) {
wxArrayString m_Temp;
for (size_t m_ProductCounter = 0; m_ProductCounter < m_Prod.size(); m_ProductCounter++) {
m_Temp.push_back(m_Prod[m_ProductCounter]->m_Name);
}
return m_Temp;
}
This function was working beautiful before but now, I dont know whats happening because every time I try something with wxStringArray or some vector, it gives this kind of error:
By the way, I've tried many ways like making this function a void return and two pass parameters and nothing, still the same error.
My current code that needs the change to wxVector to wxStringArray is this:
inline int m_MainClientClass::m_AppendProduct(m_Product* m_ProductData) {
m_GlobalProductsObject.push_back(m_ProductData);
m_Products = m_ArrayConverter(m_GlobalProductsObject);
nlohmann::json m_TempJson;
for (size_t m_ProductsSize = 0; m_ProductsSize < m_Products.size(); m_ProductsSize++){
m_TempJson.push_back(m_GlobalProductsObject[m_ProductsSize]->m_Serialize());
}
m_ProductsBuffer = m_TempJson;
m_KUtils::m_ListUpdater(m_Products, *m_ProductsList);
m_KUtils::m_UpdateConsole(*m_LogConsole, "[K] - Product [" + m_ProductData->m_Name + "] Added ", m_LogConsoleLogBuffer, m_ConsoleGreen, true);
return 1;
}
My includes if necessary:
#include <iostream>
#include <wx/wx.h>
#include "m_ProductManager.h"
#include "m_KUtils.h"
#include "m_ProductManagerWindow.h"
#include "m_ProductHeader.h"
#include "m_GlobalHeader.h"

Serializing a vector of objects with FlatBuffers

I have a vector of objects, let's call them Plumbuses, that I want to serialize with FlatBuffers. My schema for this example would be
namespace rpc;
struct Plumbus
{
dinglebopBatch:int;
fleeb:double;
}
table PlumbusesTable {
plumbuses:[Plumbus];
}
root_type PlumbusesTable;
since the root type can't be a vector. Calling flatc --cpp on this file generates plumbus_generated.h with functions such as CreatePlumbusesTableDirect.
The generated GeneratePlumbusesTableDirect function expects an argument const std::vector<const Plumbus *> *plumbuses. My idea was to simply take the addresses of the objects in the vector pbs and store them in another vector, pbPtrs. Since the buffer is created and sent away before pbs goes out of scope, I thought this would not be a problem.
#include <vector>
#include <iostream>
#include "plumbus_generated.h"
void send_plumbus(std::vector<rpc::Plumbus> pbs) {
std::vector<const rpc::Plumbus *> pbPtrs;
pbPtrs.push_back(&(pbs[0]));
pbPtrs.push_back(&(pbs[1]));
flatbuffers::FlatBufferBuilder buf(1024);
auto msg = CreatePlumbusesTableDirect(buf, &pbPtrs);
buf.Finish(msg);
void *msg_buf = buf.GetBufferPointer();
// here, I'd normally send the data through a socket
const rpc::PlumbusesTable *pbt = rpc::GetPlumbusesTable(msg_buf);
auto *pbPtrs_ = pbt->plumbuses();
for (const auto pbPtr_ : *pbPtrs_) {
std::cout << "dinglebopBatch = " << pbPtr_->dinglebopBatch() << ", fleeb = " << pbPtr_->fleeb() << std::endl;
}
}
int main(int argc, char** argv) {
rpc::Plumbus pb1(1, 2.0);
rpc::Plumbus pb2(3, 4.0);
std::vector<rpc::Plumbus> pbs = { pb1, pb2 };
send_plumbus(pbs);
}
Running this, instead of 1, 2.0, 3, and 4.0, I get
$ ./example
dinglebopBatch = 13466704, fleeb = 6.65344e-317
dinglebopBatch = 0, fleeb = 5.14322e-321
Why does it go wrong?
This looks like this relates to a bug that was recently fixed: https://github.com/google/flatbuffers/commit/fee9afd80b6358a63b92b6991d858604da524e2b
So either work with the most recent FlatBuffers, or use the version without Direct: CreatePlumbusesTable. Then you call CreateVectorOfStructs yourself.

Precision problems with `boost::geometry::difference`

Most of the time when I use boost::geometry::difference it does what I'd expect. However, when I have two polygons whose edges are almost coincident, I get some weird behavior. Consider this example:
#include <iostream>
#include <fstream>
#include <list>
#include <boost/geometry.hpp>
#include <boost/geometry/geometries/point_xy.hpp>
#include <boost/geometry/geometries/polygon.hpp>
#include <boost/geometry/geometries/multi_polygon.hpp>
using PointType = boost::geometry::model::d2::point_xy<double>;
using PolygonType = boost::geometry::model::polygon<PointType>;
using MultiPolygonType = boost::geometry::model::multi_polygon<PolygonType>;
template <typename TPolygon>
void printValidity(const TPolygon& polygons)
{
boost::geometry::validity_failure_type failure;
bool valid = boost::geometry::is_valid(polygons, failure);
if(!valid) {
std::cout << "not valid: " << failure << std::endl;
}
else {
std::cout << "valid." << std::endl;
}
}
template <typename TPolygon>
void WritePolygonsToSVG(const std::vector<TPolygon>& polygons, const std::string& filename)
{
std::ofstream svg(filename);
boost::geometry::svg_mapper<PointType> mapper(svg, 400, 400);
for(size_t polygonID = 0; polygonID < polygons.size(); ++polygonID) {
mapper.add(polygons[polygonID]);
int redValue = 50 * polygonID; // NOTE: This will break with more than 5 polygons
mapper.map(polygons[polygonID], "fill:rgb(" + std::to_string(redValue) + ",128,0);stroke:rgb(0,0,100);stroke-width:1");
}
}
int main()
{
MultiPolygonType polygon1;
boost::geometry::read_wkt("MULTIPOLYGON(((-23.8915 -12.2238,-23.7604 -10.2739,-23.1774 -7.83411,-22.196 -5.52561,-20.8436 -3.41293,-19.7976 -2.26009,-19.8108 -2.00306,24.8732 2.51519,26.0802 -9.42191,-23.6662 -14.452,-23.8915 -12.2238)))", polygon1);
WritePolygonsToSVG(polygon1, "polygon1.svg");
MultiPolygonType polygon2;
boost::geometry::read_wkt("MULTIPOLYGON(((-8.85138 -12.954,-8.89712 -12.7115,-8.9307 -12.5279,-3.35773 -12.078,-3.42937 -11.5832,-3.50013 -11.0882,-3.57007 -10.5932,-3.63925 -10.098,-3.70775 -9.60273,-3.77561 -9.10737,-3.84289 -8.61192,-3.90967 -8.1164,-3.976 -7.62081,-4.04194 -7.12517,-4.10756 -6.62948,-4.17291 -6.13375,-4.23805 -5.63799,-4.30305 -5.14222,-4.36797 -4.64643,-4.43287 -4.15064,-4.49781 -3.65485,-4.56285 -3.15909,-4.62806 -2.66331,-4.69349 -2.16756,-4.75921 -1.67185,-4.82528 -1.17619,-4.89175 -0.680597,-4.91655 -0.497015,17.8166 1.80166,17.8143 1.61653,17.8078 1.11656,17.8009 0.61658,17.7937 0.116605,17.7863 -0.383369,17.7786 -0.883343,17.7707 -1.3833,17.7627 -1.88324,17.7545 -2.38318,17.7463 -2.88312,17.7381 -3.38307,17.7299 -3.88301,17.7218 -4.38295,17.7139 -4.8829,17.706 -5.38285,17.6984 -5.88279,17.6911 -6.38274,17.684 -6.88269,17.6772 -7.38265,17.6709 -7.8826,17.6649 -8.38256,17.6595 -8.88251,17.6545 -9.38247,17.6501 -9.88244,17.6471 -10.2746,-8.85138 -12.954)))", polygon2);
WritePolygonsToSVG(polygon2, "polygon2.svg");
MultiPolygonType differencePolygon;
boost::geometry::difference(polygon1, polygon2, differencePolygon);
WritePolygonsToSVG(differencePolygon, "difference.svg");
printValidity(differencePolygon);
MultiPolygonType realDifference;
boost::geometry::read_wkt("MULTIPOLYGON(((-23.8915 -12.2238,-23.7604 -10.2739,-23.1774 -7.83411,-22.196 -5.52561,-20.8436 -3.41293,-19.7976 -2.26009,-19.8108 -2.00306,24.8732 2.51519,26.0802 -9.42191,-23.6662 -14.452,-23.8915 -12.2238),(-8.85138 -12.954,17.6471 -10.2746,17.6501 -9.88244,17.6545 -9.38247,17.6595 -8.88251,17.6649 -8.38256,17.6709 -7.8826,17.6772 -7.38265,17.684 -6.88269,17.6911 -6.38274,17.6984 -5.88279,17.706 -5.38285,17.7139 -4.8829,17.7218 -4.38295,17.7299 -3.88301,17.7381 -3.38307,17.7463 -2.88312,17.7545 -2.38318,17.7627 -1.88324,17.7707 -1.3833,17.7786 -0.883343,17.7863 -0.383369,17.7937 0.116605,17.8009 0.61658,17.8078 1.11656,17.8143 1.61653,17.8166 1.80166,-4.91655 -0.497015,-4.89175 -0.680597,-4.82528 -1.17619,-4.75921 -1.67185,-4.69349 -2.16756,-4.62806 -2.66331,-4.56285 -3.15909,-4.49781 -3.65485,-4.43287 -4.15064,-4.36797 -4.64643,-4.30305 -5.14222,-4.23805 -5.63799,-4.17291 -6.13375,-4.10756 -6.62948,-4.04194 -7.12517,-3.976 -7.62081,-3.90967 -8.1164,-3.84289 -8.61192,-3.77561 -9.10737,-3.70775 -9.60273,-3.63925 -10.098,-3.57007 -10.5932,-3.50013 -11.0882,-3.42937 -11.5832,-3.35773 -12.078,-8.9307 -12.5279,-8.89712 -12.7115,-8.85138 -12.954)))", realDifference);
WritePolygonsToSVG(realDifference, "realDifference.svg");
printValidity(realDifference);
return 0;
}
The 'differencePolygon' computed using boost::geometry::difference in this code is "valid", though it has an infinitesimally thin region:
The 'realDifference' polygon is what I output from my "real code" (where the input was slightly different due to (what I'm guessing) is the precision of the WKT writer). You can see this polygon has two infinitesimally small regions, and actually returns not valid (21: failure_self_intersections):
Is there some kind of tolerance parameter than can be set to avoid these infinitesimally small regions during the difference operation (which apparently sometimes break the polygon)? Or is there a function to "correct" the polygon by removing these self intersections? (I know the correct() function only fixes ring ordering).

boost::spirit::karma alternative selection based on properties of the input

I'm trying to write a boost::spirit::karma generator where some of the output depends on non-trivial properties of the input values.
The actual problem is part of a larger grammar, but this example has the same properties as several of the other troublesome rules and is actually one of the grammar rules that is causing me trouble.
I'll start with a minimal example that is almost what I want, and then work from there.
#include <boost/spirit/include/karma.hpp>
#include <boost/spirit/home/phoenix.hpp>
#include <boost/fusion/adapted.hpp>
#include <string>
#include <vector>
template<typename OutputIterator_T>
struct Test_Grammar :
boost::spirit::karma::grammar<OutputIterator_T, std::vector<double>()>
{
Test_Grammar() : Test_Grammar::base_type(start), start(), value()
{
namespace karma = boost::spirit::karma;
start
= *(value % karma::lit(", "))
;
value
= (karma::double_)
;
}
boost::spirit::karma::rule<OutputIterator_T, std::vector<double>()> start;
boost::spirit::karma::rule<OutputIterator_T, double()> value;
};
template <typename OutputIterator_T>
bool generate_output(OutputIterator_T& sink, std::vector<double> const& data)
{
Test_Grammar<OutputIterator_T> grammar;
return (boost::spirit::karma::generate(sink, grammar, data));
}
int main (int, char**)
{
std::string generated;
std::back_insert_iterator<std::string> sink(generated);
std::vector<double> data{1.5, 0.0, -2.5,
std::numeric_limits<float>::quiet_NaN(),
std::numeric_limits<float>::infinity()};
generate_output(sink, data);
std::cout << generated << std::endl;
return 0;
}
The above code defines a grammar that, when fed with the test data, produces the output
1.5, 0.0, -2.5, nan, inf
However, the output that I want is
1.5, 0.0, -2.5, special, special
If I replace the value part of the grammar with
value
= (&karma::double_(std::numeric_limits<double>::quiet_NaN()) <<
karma::lit("special"))
| (&karma::double_(std::numeric_limits<double>::infinity()) <<
karma::lit("special"))
| (karma::double_)
;
I get the desired behavior for infinity. However, I do not get the desired result for NaN since NaN has the property that (NaN != NaN) in comparisons. So I need a way to use the fpclassify macros/functions such as isfinite().
I should be able to get what I want by replacing the value part of the grammar with
value
= (karma::eps(...) << karma::lit("special"))
| (karma::double_)
;
However, every combination of function calls, function pointers, and bind incantations that I've tried for the ... part has resulted in compiler errors.
Any help would be much appreciated.
UPDATE:
Sehe provided an excellent general solution (which I have accepted). Thank you!
For my particular use case, I was able to further simplify sehe's answer and wanted to document that here for others.
After changing all of the includes from <boost/spirit/home/*> to <boost/spirit/include/*> and defining BOOST_SPIRIT_USE_PHOENIX_V3 before those includes, I added the following line
BOOST_PHOENIX_ADAPT_FUNCTION(bool, isfinite_, std::isfinite, 1)
and changed the value part of the grammar to this
value
%= karma::double_[karma::_pass = isfinite_(karma::_1)]
| karma::lit("special")
;
I'd use the semantic action to dynamically "fail" the double_ generator:
value
%= karma::double_ [ karma::_pass = !(isnan_(karma::_1) || isinf_(karma::_1)) ]
| karma::lit("special")
;
Now, how do we get isnan_ and isinf_ implemented? I prefer to use Phoenix V3 (which will be the default in all coming releases of Boost):
BOOST_PHOENIX_ADAPT_FUNCTION(bool, isnan_, std::isnan, 1)
BOOST_PHOENIX_ADAPT_FUNCTION(bool, isinf_, std::isinf, 1)
That's all. See it Live On Coliru
Notes
use %= to get automatic attribute propagation even though there is a semantic action
include include/*.hpp instead of home/*.hpp
Full Listing:
#define BOOST_SPIRIT_USE_PHOENIX_V3
#include <boost/spirit/include/karma.hpp>
#include <boost/spirit/include/phoenix.hpp>
#include <boost/spirit/include/phoenix_function.hpp>
#include <boost/fusion/adapted.hpp>
#include <string>
#include <vector>
#include <cmath>
BOOST_PHOENIX_ADAPT_FUNCTION(bool, isnan_, std::isnan, 1)
BOOST_PHOENIX_ADAPT_FUNCTION(bool, isinf_, std::isinf, 1)
template<typename OutputIterator_T>
struct Test_Grammar :
boost::spirit::karma::grammar<OutputIterator_T, std::vector<double>()>
{
Test_Grammar() : Test_Grammar::base_type(start), start(), value()
{
namespace karma = boost::spirit::karma;
namespace phx = boost::phoenix;
start
= *(value % karma::lit(", "))
;
value
%= karma::double_ [ karma::_pass = !(isnan_(karma::_1) || isinf_(karma::_1)) ]
| karma::lit("special")
;
}
boost::spirit::karma::rule<OutputIterator_T, std::vector<double>()> start;
boost::spirit::karma::rule<OutputIterator_T, double()> value;
};
template <typename OutputIterator_T>
bool generate_output(OutputIterator_T& sink, std::vector<double> const& data)
{
Test_Grammar<OutputIterator_T> grammar;
return (boost::spirit::karma::generate(sink, grammar, data));
}
int main (int, char**)
{
std::string generated;
std::back_insert_iterator<std::string> sink(generated);
std::vector<double> data{1.5, 0.0, -2.5,
std::numeric_limits<float>::quiet_NaN(),
std::numeric_limits<float>::infinity()};
generate_output(sink, data);
std::cout << generated << std::endl;
return 0;
}
Output
1.5, 0.0, -2.5, special, special