Search code examples
c++boostboost-spiritboost-spirit-karma

Boost Karma - non consuming predicate


I need to print a std::complex but omitting imaginary part if it's equal zero. So I have a rule with two productions:

karma::rule<OutputIterator, std::complex<double>()> complexRule = 
    '(' << double_ << ", " double_ << ')'
    | double_ << omit[double_];

This way Karma will always choose the first production, so I need some kind of predicate which will make a decission. Boost Karma tutorial comes with that solution which requires adapting std::complex as a three element tuple.

BOOST_FUSION_ADAPT_ADT(
    std::complex<double>,
    (bool, bool, obj.imag() != 0, /**/)
    (double, double, obj.real(), /**/)
    (double, double, obj.imag(), /**/)
)

but unfortunately I cannot do that since other code is using std::complex adapted as two element tuple. Is there a way to solve that problem without adding predicate straight into Fusion adapter?

I was trying to use karma::eps generator as a predicate

auto rule = eps( ... ) << '(' << double_ << ", " << double_ << ')'
          | double_ << omit[double_];

but I don't know what Phoenix expression should I put inside eps( ... ), and as Epsilon Generator doesn't consume any attribute I'm not sure if it is possible to access std::complex from it?


Solution

  • I'd personally stay away from adapting this as a sequence (I'm not sure how you adapted it as a two-element fusion sequence in the first place).

    However it's done, it won't be generic (so you'll have use separate adaptations for different type arguments (float, double, long double, boost::multiprecision::number<boost::multiprecision::cpp_dec_float<50>> etc.).

    This seems like a job for Spirit's customization points:

    namespace boost { namespace spirit { namespace traits {
    
        template <typename T>
            struct extract_from_attribute<typename std::complex<T>, boost::fusion::vector2<T, T>, void>
            {
                typedef boost::fusion::vector2<T,T> type;
    
                template <typename Context>
                    static type call(std::complex<T> const& attr, Context& context)
                    {
                        return { attr.real(), attr.imag() };
                    }
            };
    
    } } }
    

    Now you can just use any std::complex<T> with a rule/expression expecting fusion sequence:

     rule = 
            '(' << karma::double_ << ", " << karma::duplicate [ !karma::double_(0.0) << karma::double_ ] << ')' 
          | karma::double_ << karma::omit [ karma::double_ ];
    

    Note how

    • I used duplicate[] to test for 0.0 before emitting the output
    • On the other branch I used omit to consume the imaginary part without displaying anything

    Here's a full demo, Live On Coliru

    #include <boost/spirit/include/karma.hpp>
    #include <complex>
    
    namespace boost { namespace spirit { namespace traits {
    
        template <typename T>
            struct extract_from_attribute<typename std::complex<T>, boost::fusion::vector2<T, T>, void>
            {
                typedef boost::fusion::vector2<T,T> type;
    
                template <typename Context>
                    static type call(std::complex<T> const& attr, Context& context)
                    {
                        return { attr.real(), attr.imag() };
                    }
            };
    
    } } }
    
    namespace karma = boost::spirit::karma;
    
    int main()
    {
        karma::rule<boost::spirit::ostream_iterator, boost::fusion::vector2<double, double>()> 
            static const rule = 
                                '(' << karma::double_ << ", " << karma::duplicate [ !karma::double_(0.0) << karma::double_ ] << ')' 
                              | karma::double_ << karma::omit [ karma::double_ ];
    
        std::vector<std::complex<double>> const values {
                    { 123, 4 },
                    { 123, 0 },
                    { 123, std::numeric_limits<double>::infinity() },
                    { std::numeric_limits<double>::quiet_NaN(), 0 },
                    { 123, -1 },
                };
    
        std::cout << karma::format_delimited(*rule, '\n', values);
    }
    

    Output:

    (123.0, 4.0)
    123.0
    (123.0, inf)
    nan
    (123.0, -1.0)