How do I implement my own custom stream manipulator so that it is sticky. For example, I want to convert integers to binary such that:
cout << "decimal of 4: " << 4
<< "\ndecimal of 4: " << 4
<< binary << "\nbinary of 4: " << 4
<< "\nbinary of 4: " << 4
<< nobinary << "\ndecimal of 4: " << 4
<< "\ndecimal of 4: " << 4 << endl;
would return:
decimal of 4: 4
decimal of 4: 4
binary of 4: 100
binary of 4: 100
decimal of 4: 4
decimal of 4: 4
Doing the whole things is a bit involved. To make it comprehensible, I'll start with the basic stuff: Using custom formatting flags for user-defined types. Custom formatting of integers will follow below.
The IOStream classes derive [indirectly] from std::ios_base
which provides two stores for data: std::ios_base::iword()
and std::ios_base::pword()
for int
s and void*
, respectively. Maintaining allocated memory stored with std::ios_base::pword()
is non-trivial and, fortunately, not needed for this relatively simple use-case. To use these function which both return a non-const
reference to the corresponding type, you normally allocate an index using std::ios_base::xalloc()
once in your program and use it whenever you need to access your custom formatting flags. When you access a value with iword()
or pword()
initially it will be zero initialized. To put things together, here is a small program demonstrating this:
#include <iostream>
static int const index = std::ios_base::xalloc();
std::ostream& custom(std::ostream& stream) {
stream.iword(index) = 1;
return stream;
}
std::ostream& nocustom(std::ostream& stream) {
stream.iword(index) = 0;
return stream;
}
struct mytype {};
std::ostream& operator<< (std::ostream& out, mytype const&) {
return out << "custom-flag=" << out.iword(index);
}
int main()
{
std::cout << mytype() << '\n';
std::cout << custom;
std::cout << mytype() << '\n';
std::cout << nocustom;
std::cout << mytype() << '\n';
}
Now, an int
like 4
isn't a user-define type and there is already an output operator defined for these. Fortunately, you can customize the way integers get formatted using facets, more specifically using std::num_put<char>
. Now, to do so you need to do a number of steps:
std::num_put<char>
and override the do_put()
members you want to give specialized behavior to.std::locale
object using the newly create facet.std::ios_base::imbue()
the stream with the new std::locale
.To make things nicer for the user, you might want to conjure up a new std::locale
with a suitable std::num_put<char>
facet when the manipulator is used. However, before doing so, let's start off with creating a suitable facet:
#include <bitset>
#include <iostream>
#include <limits>
#include <locale>
static int const index = std::ios_base::xalloc();
class num_put
: public std::num_put<char>
{
protected:
iter_type do_put(iter_type to,
std::ios_base& fmt,
char_type fill,
long v) const
{
if (!fmt.iword(index)) {
return std::num_put<char>::do_put(to, fmt, fill, v);
}
else {
std::bitset<std::numeric_limits<long>::digits> bits(v);
size_t i(bits.size());
while (1u < i && !bits[i - 1]) {
--i;
}
for (; 0u < i; --i, ++to) {
*to = bits[i - 1]? '1': '0';
}
return to;
}
}
#if 0
// These might need to be added, too:
iter_type do_put(iter_type, std::ios_base&, char_type,
long long) const;
iter_type do_put(iter_type, std::ios_base&, char_type,
unsigned long) const;
iter_type do_put(iter_type, std::ios_base&, char_type,
unsigned long long) const;
#endif
};
std::ostream& custom(std::ostream& stream) {
stream.iword(index) = 1;
return stream;
}
std::ostream& nocustom(std::ostream& stream) {
stream.iword(index) = 0;
return stream;
}
int main()
{
std::locale loc(std::locale(), new num_put);
std::cout.imbue(loc);
std::cout << 13 << '\n';
std::cout << custom;
std::cout << 13 << '\n';
std::cout << nocustom;
std::cout << 13 << '\n';
}
What is a bit ugly is that it necessary to imbue()
the custom std::locale
to use the custom
manipulator. To get rid of this, we can just make sure the custom facet is installed in the used std::locale
and, if it is not, just install it when setting the flag:
std::ostream& custom(std::ostream& stream) {
if (!stream.iword(index)
&& 0 == dynamic_cast<num_put const*>(
&std::use_facet<std::num_put<char> >(stream.getloc()))) {
stream.imbue(std::locale(stream.getloc(), new num_put));
}
stream.iword(index) = 1;
return stream;
}
What is now left is to also override the different do_put()
members to work properly with the various unsigned
types and with long long
but this is left as an exercise.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With