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() << '
';
std::cout << custom;
std::cout << mytype() << '
';
std::cout << nocustom;
std::cout << mytype() << '
';
}
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:
- Derive a class from
std::num_put<char>
and override the do_put()
members you want to give specialized behavior to.
- Create a
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 << '
';
std::cout << custom;
std::cout << 13 << '
';
std::cout << nocustom;
std::cout << 13 << '
';
}
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.