Search code examples
c++boostboost-iostreams

Why is this Boost filtering_stream push call ambiguous?


I have the following code

std::ostringstream compressedstream; 

// Contains the source data to compress
boost::interprocess::obufferstream bufferstream((char*)bin.data(), bin.size());

boost::iostreams::filtering_stream<boost::iostreams::input> in;
in.push(boost::iostreams::gzip_compressor());
in.push(bufferstream); // The bufferstream is the 'device'
boost::iostreams::copy(in, compressedstream);

auto compresseddata = compressedstream.str();
fileContents = &std::vector<uint8_t>(compresseddata.begin(), compresseddata.end());

Which I expect to compress one std::vector<uint8_t> into another (that's the API, using files etc is not an option).

However, this fails with the following

Error   C2668   'boost::iostreams::detail::chain_client<Chain>::push': ambiguous call to overloaded function

could be 'void boost::iostreams::detail::chain_client<Chain>::push<char,std::char_traits<char>>(std::basic_ostream<char,std::char_traits<char>> &,std::streamsize,std::streamsize)'

or       'void boost::iostreams::detail::chain_client<Chain>::push<char,std::char_traits<char>>(std::basic_streambuf<char,std::char_traits<char>> &,std::streamsize,std::streamsize)'

I understand the error, that obufferstream is being seen as a basic_streambuf, and a basic_ostream, but don't know what to do about it. It's not like I can remove one of the superclasses!

What's the correct way to feed a bufferstream into a filtering_stream?


Solution

  • A possible way would be to disambiguate, e.g. using

    in.push(bufferstream.rdbuf());
    

    That said, there seems to be a lot of unnecessary steps/complexity. If you wanted to implement this:

    using Data = std::vector<uint8_t>;
    Data compress(Data const& bin);
    

    I'd do it without bufferstream (and the interprocess library) in the first place:

    Data compress(Data const& bin) {
        std::vector<char> bout;
        bio::array_source src(reinterpret_cast<char const*>(bin.data()), bin.size());
        bio::filtering_ostreambuf dst{bio::gzip_compressor()};
        dst.push(bio::back_inserter(bout));
        copy(src, dst);
        return Data(bout.begin(), bout.end());
    }
    

    This sadly requires a copy because back_insert_device would default char_type to uint8_t for Data. That's a shame. We can "trivially" 🤔 work around it by defining an adhoc converting output "device", like so:

    Data compress(Data const& bin) {
        Data bout;
    
        bio::array_source src(reinterpret_cast<char const*>(bin.data()), bin.size());
        bio::filtering_ostreambuf dst{bio::gzip_compressor()};
    
        struct inserter {
            using char_type = char;
            using category  = bio::sink_tag;
            std::streamsize write(char_type const* p, std::streamsize n) {
                _c.insert(_c.end(), p, p + n);
                return n;
            }
            Data& _c;
        };
        dst.push(inserter{bout});
        copy(src, dst);
        return bout;
    }
    

    See it Live On Coliru

    #include <boost/iostreams/copy.hpp>
    #include <boost/iostreams/device/array.hpp>
    #include <boost/iostreams/filter/gzip.hpp>
    #include <boost/iostreams/filtering_streambuf.hpp>
    #include <sstream>
    #include <vector>
    
    namespace bio = boost::iostreams;
    
    using Data = std::vector<uint8_t>;
    Data compress(Data const& bin) {
        Data bout;
    
        bio::array_source src(reinterpret_cast<char const*>(bin.data()), bin.size());
        bio::filtering_ostreambuf dst{bio::gzip_compressor()};
    
        struct inserter {
            using char_type = char;
            using category  = bio::sink_tag;
            std::streamsize write(char_type const* p, std::streamsize n) {
                _c.insert(_c.end(), p, p + n);
                return n;
            }
            Data& _c;
        };
        dst.push(inserter{bout});
        copy(src, dst);
        return bout;
    }
    
    #include <fmt/ranges.h>
    int main() {
        auto compressed = compress({'H', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd', '!', '\n'});
        fmt::print("Compressed: {::02x}\n", compressed);
    }
    

    Printing

    Compressed: [1f, 8b, 08, 00, 00, 00, 00, 00, 00, ff, f3, 48, cd, c9, c9, 57, 28, cf, 2f, ca, 49, 51, e4, 02, 00, 41, e4, a9, b2, 0d, 00, 00, 00]
    

    Disregard the GCC version on Coliru complaining that category is unused - it's actually not.