How to round a number during QI parsing with boost?

Viewed 29

With a simple Boost qi grammar, how can I make it rounding my number?

This is the parser:

factor =
            float_                          [_val = _1]
            |   ('-' >> factor              [_val = -_1])
            |   ('+' >> factor              [_val = _1])
            ;

This can parse a float and it can be also negative.

I want to round the float, so I would add something like this to the grammar:

|   ('~' >> factor              [_val = round(_1)])

But this results a compile-time error:

no type named ‘__type’ in ‘struct __gnu_cxx::__enable_if<false, double>’

This error is not too informative for me, can you please help? I want to be able to round a number, ie:

~1.8 -> 2
~1.2 -> 1

Note: I'm parsing with phrase_parse.

1 Answers

Semantic actions require Phoenix Actors, which are deferred functions.

Options:

  1. Adaptation macros https://www.boost.org/doc/libs/1_73_0/libs/phoenix/doc/html/phoenix/modules/function/adapting_functions.html
  2. phoenix::function<>
  3. phoenix::bind
  4. write your own, see for more details https://www.boost.org/doc/libs/1_68_0/libs/spirit/doc/html/spirit/qi/tutorials/semantic_actions.html#spirit.qi.tutorials.semantic_actions.examples_of_semantic_actions

Simplified Test Bed

Just parsing a number:

#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix.hpp>
namespace qi = boost::spirit::qi;

int main() {
    std::string s = "1.75";

    double v;
    if (qi::parse(begin(s), end(s), qi::double_, v)) {
        std::cout << "Parsed: " << v << "\n";
    }
}

Prints Live On Coliru:

Parsed: 1.75

Adaptation

Using the macros:

#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix.hpp>
#include <cmath>
namespace qi = boost::spirit::qi;

int main() {
    std::string s = "1.75";

    double v;
    if (qi::parse(begin(s), end(s), qi::double_, v)) {
        std::cout << "Parsed: " << v << "\n";
        std::cout << "Rounded: " << round(v) << "\n";
    }
}

Prints Live On Coliru:

Parsed: 2

function<>

You can get away with hardcoding a signature here:

boost::phoenix::function<double(*)(double)> round_(::round);

However the real power comes with polymorphic calleables:

struct round_f {
    template <typename T> auto operator()(T const& v) const {
        using std::round; // activate ADL
        return round(v);
    }
};
boost::phoenix::function<round_f> round_{};

Now you can use the round_ actor on any type that has a free-function overload round overload that is compatible. Handy if tomorrow you decide to parse long double, float or boost::multiprecision::cpp_dec_float.

See it Live On Coliru

#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix.hpp>
#include <cmath>
namespace qi = boost::spirit::qi;

struct round_f {
    template <typename T> auto operator()(T const& v) const {
        using std::round; // activate ADL
        return round(v);
    }
};
boost::phoenix::function<round_f> round_{};

int main() {
    std::string s = "1.75";

    double v;
    using namespace qi::labels;
    if (qi::parse(begin(s), end(s), qi::double_ [ _val = round_(_1) ], v)) {
        std::cout << "Parsed: " << v << "\n";
    }
}

Prints

Parsed: 2

Using phoenix::bind

As a lower-level building block, you can bind unwrapped calleables:

if (qi::parse(begin(s), end(s), qi::double_ 
            [ _val = phoenix::bind(round_f{}, _1) ], v))
{
    std::cout << "Parsed: " << v << "\n";
}

If you don't mind ugly:

if (qi::parse(begin(s), end(s), qi::double_ 
    [ _val = phoenix::bind(static_cast<double(&)(double)>(std::round), _1) ], v))
{
    std::cout << "Parsed: " << v << "\n";
}

See both Live On Coliru

Related