Interesting.
Going down the rabbit hole[1] it turns out that close_impl<any_tag>
is finally reached for the ofstream wrapped deep inside the chain_buf inside the filtering_streambuf. The implementation reads:
template<>
struct close_impl<any_tag> {
template<typename T>
static void close(T& t, BOOST_IOS::openmode which)
{
if (which == BOOST_IOS::out)
iostreams::flush(t);
}
template<typename T, typename Sink>
static void close(T& t, Sink& snk, BOOST_IOS::openmode which)
{
if (which == BOOST_IOS::out) {
non_blocking_adapter<Sink> nb(snk);
iostreams::flush(t, nb);
}
}
};
So, as you can see, the documented behaviour is actually just that the linked output stream buffer(s) are flushed (there's also a synch on the containing entity prior to that call, IIRC).
I completely agree that this could have been made a whole lot more explicit.
Reading the TMP code that decides on the specialization:
template<typename T>
struct close_tag {
typedef typename category_of<T>::type category;
typedef typename detail::unwrapped_type<T>::type unwrapped;
typedef typename
iostreams::select<
mpl::not_< is_convertible<category, closable_tag> >,
any_tag,
mpl::or_<
is_boost_stream<unwrapped>,
is_boost_stream_buffer<unwrapped>
>,
close_boost_stream,
mpl::or_<
is_filtering_stream<unwrapped>,
is_filtering_streambuf<unwrapped>
>,
close_filtering_stream,
mpl::or_<
is_convertible<category, two_sequence>,
is_convertible<category, dual_use>
>,
two_sequence,
else_,
closable_tag
>::type type;
};
Several workarounds come to mind:
define a specialization of
close_tag<>
forstd::ofstream
that actually returns a different tag and make it so that it gets closed (I recommend against this since it can have unintended effects by going against the assumptions held by the devs of Boost Iostreams)use a boost class for the output stream:
#include <iostream>
#include <fstream>
#include <boost/iostreams/filtering_streambuf.hpp>
#include <boost/iostreams/copy.hpp>
#include <boost/iostreams/device/file.hpp>
#include <boost/iostreams/filter/gzip.hpp>
using namespace std;
int main(void)
{
cout << boolalpha;
ifstream ifs("output", ios::binary);
boost::iostreams::file_sink ofile("output.boost.gz");
boost::iostreams::filtering_streambuf<boost::iostreams::output> out;
out.set_auto_close(true);
out.push(boost::iostreams::gzip_compressor());
out.push(ofile);
cout << "out.is_complete(): " << out.is_complete() << endl;
cout << "ifs.is_open()? " << ifs.is_open() << endl;
cout << "ofile.is_open()? " << ofile.is_open() << endl;
boost::iostreams::copy(ifs, out);
cout << "out.is_complete(): " << out.is_complete() << endl;
cout << "ifs.is_open()? " << ifs.is_open() << endl;
cout << "ofile.is_open()? " << ofile.is_open() << endl;
}
See it Live on Coliru
[1] It is a surprisingly large rabbit hole, I must add. I wonder what benefit all this genericity really has