Oddly enough, at least since everything is really designed to work, none of this should directly include iostreams and / or streambufs.
I would think of iostream as a match-maker class. Iostream has streambuf, which provides a buffer interface for some external data source / receiver. It also has a locale that handles all formatting. Iostream is a little more than the leader of a playground that keeps these two playing together beautifully (so to speak). Since you are dealing with data formatting, all of this (or should be) handled locally.
The locale is not monolithic, although it consists of several facet s, each of which is devoted to one specific part of data formatting. In this case, the part that you probably care about is the codecvt facet, which is used (almost exclusively) to translate between the external and internal representations of data read from / to iostreams.
However, better or worse, however, a language can only contain one piece of codecvt at a time, and not a chain of them, as you contemplate. Thus, what you really need / need is a wrapper class that provides the codec as an external interface, but allows you to bind an arbitrary set of transformations to data during I / O.
For utf-to-utf conversion, Boost.locale provides the utf_to_utf function and the codecvt wrapper code, so this part of the conversion is simple and straightforward.
So that someone would not assume that such things will be done using the ICU, I will add that Boost.Locale is pretty much a wrapper around the ICU, so this is more or less the same answer, but in a more C + friendly form + (whereas the ICU itself is quite similar to Java, and that's all, except for being openly hostile to C ++).
The other side is that writing a codecvt grant adds more complexity to a fairly simple task. Filtering streambuf (for one example) is usually much easier to write. It is still not as simple as we would like, but not as bad as the face of codecvt. As mentioned in @Flexo, the Boost iostreams library already includes a streambuf filter that performs ZIP compression. Doing something similar with lzma (or lzh, arithmetic, etc.) is relatively easy, at least assuming you have compression functions that are easy to use (you basically just supply them with an input buffer and they provide a buffer results).