Sticky custom stream manipulator
Asked Answered
c++
R

1

12

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
Rudderpost answered 11/11, 2012 at 20:36 Comment(11)
I would kill the nobinary notion and let them use std::dec etc.Matchmark
Do you want it to stick even after the end of the statement?Lizliza
BTW, don't use std::endl.Hayner
@VaughnCato: Why would it matter?Hayner
@DietmarKühl: There are some template tricks that you could use to make it persist till the end of the expression.Lizliza
@DietmarKühl why not use std::endl? did you mean generally or in the case of a custom manipulator?Schwann
@VaughnCato: Why would advise someone to use a severely broken hack when there exists a perfectly good and working solution?Hayner
@Caribou: I mean generally! Click the link mentioned above already for a full discussion of the problem. If you want to see the full discussion here: ask it and I'll answer with a copy&paste job ;)Hayner
@DietmarKühl: Your solution is certainly preferred, but I wasn't familiar with it.Lizliza
@DietmarKühl sorry didn't see the link - thx ;) I hadn't thought about that aspect of endlSchwann
Don't make destructive edits. This is not the way to get rid of a question that no longer applies.Janniejanos
A
17

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 ints 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:

  1. Derive a class from std::num_put<char> and override the do_put() members you want to give specialized behavior to.
  2. Create a std::locale object using the newly create facet.
  3. 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.

Attis answered 11/11, 2012 at 21:25 Comment(2)
The thing I did not get was the dynamic_cast. What is that for?Basenji
@0x499602D2: All the dynamic_cast does it verifying whether a custom std::locale needs to be imbue()ed: If there is already a suitable facet installed, there is no need to imbue() a new facet. Looking at the edit, it actually seems that the earlier edit enbugged the code the previously correct code! The dynamic_cast tried to test for the custom facet num_put, not for std::num_put<char>!Hayner

© 2022 - 2024 — McMap. All rights reserved.