Search code examples
c++boost-spirit

Making boost::spirit::hold_any accept vectors


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 to 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?


Solution

  • 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)

    Incidentally, the whole question was about streaming any in the first place, so the answer was on-point there.

    The code further drives home the assumption:

    // these functions have been added in the assumption that the embedded
    // type has a corresponding operator defined, which is completely safe
    // because spirit::hold_any is used only in contexts where these operators
    // do exist
        template <typename Char_>
        friend inline std::basic_istream<Char_>&
        operator>> (std::basic_istream<Char_>& i, basic_hold_any<Char_>& obj)
        {
            return obj.table->stream_in(i, &obj.object);
        }
    
        template <typename Char_>
        friend inline std::basic_ostream<Char_>&
        operator<< (std::basic_ostream<Char_>& o, basic_hold_any<Char_> const& obj)
        {
            return obj.table->stream_out(o, &obj.object);
        }
    

    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.