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

Avoiding attribute copies with karma generators


I'm using karma to generate representations of large structs, but the structs are being copied during generation. I don't think they need to be, so was wondering how to avoid it.

The quick example below prints "Copy!", as the target struct is copied in rule::generate:

namespace karma = spirit::karma;
namespace phoenix = boost::phoenix;

struct foo
{
    foo() { }
    foo( foo const &other ) { std::cout << "Copy!"; }
    int f() const { return 42; }
};

std::string output;
typedef std::back_insert_iterator< std::string > iterator;
karma::rule< iterator, foo() > foo_rule = 
    karma::int_[ karma::_1 = phoenix::bind( &foo::f, karma::_val ) ];
foo my_foo;
iterator it( output );
karma::generate( it, foo_rule, my_foo );

I can stop the copy by declaring foo_rule's attribute by reference:

karma::rule< iterator, foo &() > foo_rule

but that doesn't work with a vector [obviously the foos are therefore copyable, but may be cheap to copy at vector construction, but expensive to copy at generate time :-)]

The example below prints 'Copy!' five times during generation (that is, ignoring copies during vector ctor); 10 times if foo_rule's attribute isn't a reference:

std::vector<foo> my_vec_foo(5);
karma::rule< iterator, std::vector<foo>() > vec_foo_rule = *foo_rule;
karma::generate(it, vec_foo_rule, my_vec_foo);

Having both rules take references doesn't compile with Boost 1.47 on VC 2008. That is, with:

karma::rule< iterator, foo &() > foo_rule /* = ... */;
karma::rule< iterator, std::vector<foo> &() > vec_foo_rule /* = ... */;

I get extract_from_container instantiated with Attribute = std::vector<foo> and Exposed=std::vector<foo> &. On line 131 of extract_from.hpp, it tries to form Exposed const & and the compiler fails when creating refrence-to-reference.

I feel like I'm missing something, so any pointers would be greatly appreciated!


Solution

  • I'm sure you've tried it, but i'll say it nonetheless. Have you tried as follows:

    std::vector<foo> my_vec_foo(5);
    karma::rule< iterator, std::vector<foo>&() > vec_foo_rule = *foo_rule;
    karma::generate(it, vec_foo_rule, my_vec_foo);
    

    Update I just tested it with the below snippet (g++ 4.6 with Boost 1.47.0). It confirms that the above works. However, there is room for confusion, as the std::vector<foo> my_vec_foo(5) will also show 5 copies being made. See the BIG LETTER warning in the code and the output:

    #include <boost/spirit/include/karma.hpp>
    #include <boost/spirit/include/phoenix.hpp>
    
    namespace karma = boost::spirit::karma;
    namespace phoenix = boost::phoenix;
    
    struct foo
    {
        foo() { }
        foo( foo const &other ) { std::cerr << "Copy!\n"; }
        int f() const { return 42; }
    };
    
    int main()
    {
        std::string output;
        typedef std::back_insert_iterator< std::string > iterator;
        iterator it( output );
        karma::rule< iterator, foo&() > foo_rule = 
            karma::int_[ karma::_1 = phoenix::bind( &foo::f, karma::_val ) ];
    
        foo my_foo;
        karma::generate( it, foo_rule, my_foo );
    
        std::vector<foo> my_vec_foo(5);
    
        std::cerr << "\nSTART WATCHING NOW" << std::endl;
    
        karma::rule< iterator, std::vector<foo>&() > vec_foo_rule = *foo_rule;
        karma::generate(it, vec_foo_rule, my_vec_foo);
    }
    

    Output:

    Copy!
    Copy!
    Copy!
    Copy!
    Copy!
    
    START WATCHING NOW