Search code examples
c++boost-spiritboost-variantboost-spirit-karma

boost::spirit::karma using the alternatives operator (|) with conditions


I'm trying to generate a string from my own class called Value using boost::spirit::karma, but i got stuck with this. I've tried to extract my problem into a simple example.

I want to generate a String with karma from instances of the following class:

class Value
{
public:

    enum ValueType
    {
        BoolType,
        NumericType
    };

    Value(bool b) : type_(BoolType), value_(b) {}
    Value(const double d) : type_(NumericType), value_(d) {};

    ValueType type() { return type_; }

    operator bool() { return boost::get<bool>(value_); }
    operator double() { return boost::get<double>(value_); }

private:
    ValueType type_;
    boost::variant<bool, double> value_;

};

Here you can see what I'm tying to do:

int main()
{
    using karma::bool_;
    using karma::double_;
    using karma::rule;
    using karma::eps;

    std::string generated;
    std::back_insert_iterator<std::string> sink(generated);

    rule<std::back_insert_iterator<std::string>, Value()> value_rule = bool_ | double_;

    Value bool_value = Value(true);
    Value double_value = Value(5.0);

    karma::generate(sink, value_rule, bool_value);
    std::cout << generated << "\n";

    generated.clear();

    karma::generate(sink, value_rule, double_value);
    std::cout << generated << "\n";

    return 0;
}

The first call to karma::generate() works fine because the value is a bool and the first generator in my rule also "consumes" a bool. But the second karma::generate() fails with boost::bad_get because karma tries to eat a bool and calls therefore Value::operator bool().

My next thought was to modify my generator rule and use the eps() generator together with a condition but here i got stuck:

value_rule = (eps( ... ) << bool_) | (eps( ... ) << double_);

I'm unable to fill the brackets of the eps generator with sth. like this (of course not working):

eps(value.type() == BoolType)

I've tried to get into boost::phoenix, but my brain seems not to be ready for things like this.

Please help me!

here is my full example (compiling but not working): main.cpp


Solution

  • The simplest thing that comes to mind is: use the value_ variant (as variants are very well-supported by Karma).

    Using a phoenix bind inside the semantic action would work:

    rule<std::back_insert_iterator<std::string>, Value()> value_rule;
    
    value_rule = (bool_ | double_) 
        [ _1 = phx::bind(&Value::value_, _val) ];
    

    Although this requires exposing value_ (to friends e.g.) so you might prefer an accessor method.

    Here is a working example http://liveworkspace.org/code/22ab2093ad9bd3b03e55a7f3dde952f8

    #include <boost/spirit/include/karma.hpp>
    #include <boost/spirit/include/phoenix.hpp>
    #include <boost/variant.hpp>
    
    #include <iostream>
    #include <string>
    
    namespace karma = boost::spirit::karma;
    namespace phx = boost::phoenix;
    
    class Value
    {
    public:
    
        enum ValueType
        {
            BoolType,
            NumericType
        };
    
        Value(bool b) : type_(BoolType), value_(b) {}
        Value(double d) : type_(NumericType), value_(d) {};
    
        ValueType type() { return type_; }
    
        operator bool()   { return boost::get<bool>(value_);   }
        operator double() { return boost::get<double>(value_); }
    
      private:
        ValueType type_;
    
        friend int main();
        boost::variant<bool, double> value_;
    };
    
    namespace karma = boost::spirit::karma;
    
    int main()
    {
        using namespace karma;
        std::string generated;
        std::back_insert_iterator<std::string> sink(generated);
    
        rule<std::back_insert_iterator<std::string>, Value()> value_rule;
    
        value_rule = (bool_ | double_) 
            [ _1 = phx::bind(&Value::value_, _val) ];
    
        Value bool_value = Value(true);
        Value double_value = Value(5.0);
    
        karma::generate(sink, value_rule, bool_value);
        std::cout << generated << "\n";
    
        generated.clear();
        karma::generate(sink, value_rule, double_value);
        std::cout << generated << "\n";
    
        return 0;
    }
    

    Output

    true
    5.0
    

    Off-topic: May I suggest marking the conversion operators explicit (at the very least), to avoid nasty surprises?