Search code examples
c++boostboost-spirit

Boost Karma: generate default text when boost::optional is unset


Consider the following program:

using FooVariant = boost::variant<std::string, int>;
using FooOptional = boost::optional<FooVariant>;

template<typename OutputIt = boost::spirit::ostream_iterator>
struct FooGenerator
    : boost::spirit::karma::grammar<OutputIt, FooOptional()>
{
    FooGenerator()
        : FooGenerator::base_type(start_)
    {
        namespace bsk = boost::spirit::karma;

        foovar_ = bsk::auto_;
        start_ = -foovar_;
    }

    boost::spirit::karma::rule<OutputIt, FooVariant()> foovar_;
    boost::spirit::karma::rule<OutputIt, FooOptional()> start_;
};

int main()
{
    FooVariant fv = "foo";
    FooOptional fo = fv;
    std::cout << boost::spirit::karma::format(FooGenerator<>(), fo) << std::endl;
}

As expected this will print foo. Likewise if I initialize fo simply with:

FooOptional fo;

Then the program will print nothing, again as expected. But instead of printing nothing, I would like to print - instead. So, I changed my rule for start_ to:

start_ = (foovar_ | '-');

But this leads to a compilation error:

alternative_function.hpp:127:34: error: no member named 'is_compatible' in 'boost::spirit::traits::compute_compatible_component, int>, boost::optional, int> >, boost::spirit::karma::domain>' if (!component_type::is_compatible(spirit::traits::which(attr_))) ~~~~~~~~~~~~~~~~^

I also noticed that if I remove the FooVariant and instead make FooOptional = boost::optional<int> and update my generator, I can produce a crash if I pass it an unset optional. For example:

int main()
{
    FooOptional fo;
    std::cout << boost::spirit::karma::format(FooGenerator<>(), fo) << std::endl;
}

Which leads me to believe I'm using the optional generation incorrectly. What is the right way to do this?

UPDATE

Investigating a little more I discovered something interesting. My modified code is:

using FooVariant = boost::variant<std::string, int>;
using FooOptional = boost::optional<int>;

template<typename OutputIt = boost::spirit::ostream_iterator>
struct FooGenerator
    : boost::spirit::karma::grammar<OutputIt, FooOptional()>
{
    FooGenerator()
        : FooGenerator::base_type(start_)
    {
        namespace bsk = boost::spirit::karma;

        foovar_ = bsk::int_;
        start_ = (bsk::int_ | '-');
    }

    boost::spirit::karma::rule<OutputIt, int()> foovar_;
    boost::spirit::karma::rule<OutputIt, FooOptional()> start_;
};

int main()
{
    FooOptional fo;
    std::cout << boost::spirit::karma::format(FooGenerator<>(), fo) << std::endl;
}

This works in that it will print the - or an integer value if one is assigned (which is not in the code pasted). However when I change my start_ rule to this:

start_ = (foovar_ | '-');

I get a crash on an empty value.


Solution

  • I agree that this seems not to work as you'd hope. Maybe a pragmatic simplification is to express "Nil" as a variant element type:

    struct Nil final {};
    
    using FooVariant = boost::variant<Nil, std::string, int>;
    

    Now a default-contructed FooVariant will contain Nil. And the rule simply becomes:

        start_  = string_ | bsk::int_ | "(unset)";
    

    Demo

    Live On Wandbox

    #include <boost/spirit/include/karma.hpp>
    
    struct Nil final {};
    
    using FooVariant = boost::variant<Nil, std::string, int>;
    
    template<typename OutputIt = boost::spirit::ostream_iterator>
    struct FooGenerator : boost::spirit::karma::grammar<OutputIt, FooVariant()>
    {
        FooGenerator()
            : FooGenerator::base_type(start_)
        {
            namespace bsk = boost::spirit::karma;
    
            string_ = '"' << *('\\' << bsk::char_("\\\"") | bsk::print | "\\x" << bsk::right_align(2, '0')[bsk::hex]) << '"';
            start_  = string_ | bsk::int_ | "(unset)";
        }
    
        boost::spirit::karma::rule<OutputIt, std::string()> string_;
        boost::spirit::karma::rule<OutputIt, FooVariant()> start_;
    };
    
    int main() {
        for (auto fo : { FooVariant{}, {FooVariant{42}}, {FooVariant{"Hello\r\nWorld!"}} }) {
            std::cout << boost::spirit::karma::format(FooGenerator<>(), fo) << std::endl;
        }
    }
    

    Prints

    (unset)
    42
    "Hello\x0d\x0aWorld!"