Home > Blockchain >  Boost Karma for a boost::variant containing a custom class
Boost Karma for a boost::variant containing a custom class

Time:07-25

I'd like to investigate boost::spirit::karma::generate as a replacement for std::stringstream for a boost::variant containing, apart from familiar types such as int and double, one or more custom classes (e.g. A). However I'm unable to even compile the code once I include one or more custom classes in the variant.

#include <iostream>
#include <boost/spirit/include/karma.hpp>
#include <boost/variant.hpp>

struct A {
    double d;
    explicit A(const double d) : d(d) {}

    friend std::ostream& operator<<(std::ostream& os, A const& m) {
        return os << "{" << m.d << "}";
    }
};

BOOST_FUSION_ADAPT_STRUCT(A, d)

using Variant = boost::variant<int, double, A>;

void test_stringstream(const Variant &v) {
    std::stringstream os;
    os << v;
    std::cout << os.str() << std::endl;
}

void test_karma(const Variant &v) {
    std::string str;
    boost::spirit::karma::generate(std::back_inserter(str), v);
    std::cout << str << std::endl;
}

int main() {
    A a(double(1.0));
    std::cout << a << std::endl;
    test_stringstream(Variant(a));
    test_karma(Variant(a));
}

I'd expect the output:

{1}
{1}
{1}

The error output begins with

mpl_iterator.hpp:45:24: error: no matching constructor for initialization of 'boost::fusion::mpl_iterator<boost::mpl::l_iter<boost::mpl::l_item<mpl_::long_<1>, A, boost::mpl::l_end>>>::deref<boost::fusion::mpl_iterator<boost::mpl::l_iter<boost::mpl::l_item<mpl_::long_<1>, A, boost::mpl::l_end>>>>::type' (aka 'A')
                return type();

Apart from custom classes, I'd also like to enquire how one could make Enums 'streamable' as it were via boost::spirit::karma::generate.

CodePudding user response:

The error message informs you that A is not default-constructible. It's a long type expression, but helpfully summarized it for you: (aka 'A').

Adding a default value for d fixes it:

explicit A(double d = {}) : d(d) {}

Now with

std::cout << "iostream:     " << a << std::endl;
std::cout << "stringstream: "; test_stringstream(Variant(a));
std::cout << "karma:        "; test_karma(Variant(a));

You'd see (Live)

iostream:     {1}
stringstream: {1}
karma:

You didn't pass any karma expression to generate. Let's add:

void test_karma(const Variant& v) {
    namespace k = boost::spirit::karma;
    std::string str;
    k::generate(std::back_inserter(str), k::stream, v);
    std::cout << str << std::endl;
}

Now you get the same output. Note that the Fusion adaptation is completely unnecessary here, don't actually ever deal with A in your karma expression. It will effectively amount to the stringstream implementation, but with extra steps, so it will be slower.

Demo

Live

Prints

iostream:     {1}
stringstream: {1}
karma:        {1}

Suggestions

I'd suggest using Boost lexical_cast here. It's almost guaranteed to be faster and certainly less ... clumsy:

Live On Coliru

#include <boost/lexical_cast.hpp>
#include <boost/variant.hpp>
#include <iostream>

struct A {
    explicit A(double d = {}) : d(d) {}

  private:
    double d;
    friend std::ostream& operator<<(std::ostream& os, A const& m) {
        return os << "{" << m.d << "}";
    }
};

using Variant = boost::variant<int, double, A>;

int main() {
    Variant vv[]{42, 3.14, A(1.0)};
    for (Variant a : vv)
        std::cout << boost::lexical_cast<std::string>(a) << "\n";
}

Prints

42
3.14
{1}
  • Related