Search code examples
c++booststreambuf

Return boost streambuf from function


I am trying to wrap up a code that read gz files into a function, source code is taken from https://techoverflow.net/2013/11/03/c-iterating-lines-in-a-gz-file-using-boostiostreams/

My try

boost::iostreams::filtering_streambuf<boost::iostreams::input> func(std::string filename);
boost::iostreams::filtering_streambuf<boost::iostreams::input> func(std::string filename)
{
  std::ifstream file(filename, std::ios_base::in | std::ios_base::binary);
  boost::iostreams::filtering_streambuf<boost::iostreams::input> inbuf;
  inbuf.push(boost::iostreams::gzip_decompressor());
  inbuf.push(file);
  return inbuf;

}
void mymainfunc(std::string filename)
{

  //Convert streambuf to istream
  std::istream ifstrm( func( filename));
  std::string line;
  while(std::getline(ifstrm, line)) {
        std::cout << line << std::endl;
    }
}

Code runs fine if not run through the function, i am doing something wrong in the return type I think. Error: https://pastebin.com/kFpjYG0M


Solution

  • Streams are not copyable. In fact, this filteringstreambuf is not even movable.

    So in this case, you will want to dynamically allocate and return by smart-pointer. However, even just returning the filtering streambuf will not work, because it would hold a reference to the ifstream. And that's a local.

    So, maybe you need to package it up:

    Live On Coliru

    #include <boost/iostreams/filtering_streambuf.hpp>
    #include <boost/iostreams/filter/gzip.hpp>
    #include <fstream>
    
    namespace bio = boost::iostreams;
    
    struct MySource {
        using fisb = bio::filtering_istreambuf;
    
        struct State {
            State(std::string filename) : ifs(filename, std::ios::binary) {
                buf.push(bio::gzip_decompressor());
                buf.push(ifs);
            }
    
            fisb buf;
            std::ifstream ifs;
            std::istream is { &buf };
        };
    
        std::unique_ptr<State> _state;
    
        operator std::istream&() const { return _state->is; }
    };
    
    MySource func(std::string filename) {
        auto inbuf = std::make_unique<MySource::State>(filename);
        return {std::move(inbuf)};
    }
    
    #include <iostream>
    void mymainfunc(std::string filename)
    {
        auto source = func(filename);
        std::istream& is = source;
    
        std::string line;
        while(std::getline(is, line)) {
            std::cout << line << std::endl;
        }
    }
    
    int main(){
        mymainfunc("test.cpp.gz");
    }
    

    Alternative #1

    You can simplify that:

    Live On Coliru

    struct MySource {
        struct State {
            State(std::string filename) : ifs(filename, std::ios::binary) {
                is.push(bio::gzip_decompressor());
                is.push(ifs);
            }
    
            std::ifstream ifs;
            bio::filtering_istream is;
        };
    
        std::unique_ptr<State> _state;
    
        operator std::istream&() const { return _state->is; }
    };
    

    Not dealing with the streambuffer separately makes it simpler.

    Alternative #2

    Not copying the whole thing has its own elegance:

    Live On Coliru

    #include <boost/iostreams/filtering_stream.hpp>
    #include <boost/iostreams/filter/gzip.hpp>
    #include <fstream>
    #include <iostream>
    
    void mymainfunc(std::istream& is) {
        std::string line;
        while(std::getline(is, line)) {
            std::cout << line << std::endl;
        }
    }
    
    namespace bio = boost::iostreams;
    
    int main(){
        std::ifstream ifs("test.cpp.gz", std::ios::binary);
    
        bio::filtering_istream is;
        is.push(bio::gzip_decompressor());
        is.push(ifs);
    
        mymainfunc(is);
    }