Home > Software engineering >  Making boost::spirit::hold_any accept vectors
Making boost::spirit::hold_any accept vectors

Time:07-26

According to its author @hkaiser, here boost::spirit::hold_any is a more performant alternative to boost::any and can, for the most part, be used as a drop-in replacement for the latter. I'm interested in it since it purports to allow for easy output streaming, a feature that boost::any lacks.

While I'm able to input simple types like int into hold_any, I can't seem to get it hold a container such as std::vector<int> for example. Here'e the code

#include <iostream>
#include <boost/spirit/home/support/detail/hold_any.hpp> // v1.79
#include <vector>
using any = boost::spirit::hold_any;

int main() {
    int a1 = 1;
    any v1(a1); // OK
    std::cout << v1 << std::endl; // OK
    std::vector<int> w2 = {5,6};
    any v2(w2); // compilation error - invalid operands to binary expression ('std::basic_istream<char>' and 'std::vector<int>')
    std::cout << v2 << std::endl;
}

which fails to compile with

hold_any.hpp:155:23: error: invalid operands to binary expression ('std::basic_istream<char>' and 'std::vector<int>')
                    i >> *static_cast<T*>(*obj);

Presumably, the std::vector<int> needs to be made istream streamable though I'm not sure how to proceed. How does one make this work?

CodePudding user response:

I was able to make the above code work by making a generic vector std::vector<T> both output and input streamable.

#include <iostream>
#include <boost/spirit/home/support/detail/hold_any.hpp>
#include <vector>
using any = boost::spirit::hold_any;

namespace std {
    // input stream
    template<typename T>
    std::istream& operator >> ( std::istream& ins, std::vector<T>& p ) {
        size_t sz;
        ins >> sz;
        for ( size_t i = 0; i < sz;   i ) {
            T tmp;
            ins >> tmp;
            p.push_back( tmp );
        }
        return ins;
    }
    // output stream
    template<typename T>
    std::ostream& operator << ( std::ostream& outs, const std::vector<T>& p ) {
        outs << "[";
        for ( size_t i = 0; i < p.size();   i ) {
            outs << p[i];
            if ( i != p.size() - 1 )
                outs << " ";
            else
                outs << "]";
        }
        return outs;
    }
}

I'd appreciate any comments on how to make the above more performant. boost::iostreams perhaps?

CodePudding user response:

Firstly, that advice is 12 years old. Since then std::any was even standardized. I would not assume that hold_any is still the better choice (on the contrary).

Also, note that the answer you implied contains the exact explanation:

This class has two differences if compared to boost::any:

  • it utilizes the small object optimization idiom and a couple of other optimization tricks, making spirit::hold_any smaller and faster than boost::any
  • it has the streaming operators (operator<<() and operator>>()) defined, allowing to input and output a spirit::hold_any seemlessly.

(emphasis mine)

So, indeed that explains the requirement. It's a bit unfortunate that the implementation is not SFINAE-ed so that you'd only run into the limitation if you used the stream_in/stream_out operations, but here we are.

  • Related