Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Custom stream manipulator for streaming integers in any base

Tags:

c++

iostream

I can make an std::ostream object output integer numbers in hex, for example

std::cout << std::hex << 0xabc; //prints `abc`, not the base-10 representation

Is there any manipulator that is universal for all bases? Something like

std::cout << std::base(4) << 20; //I want this to output 110

If there is one, then I have no further question. If there isn't one, then can I write one? Won't it require me to access private implementation details of std::ostream?

Note that I know I can write a function that takes a number and converts it to a string which is the representation of that number in any base. Or I can use one that already exists. I am asking about custom stream manipulators - are they possible?

like image 962
Armen Tsirunyan Avatar asked Jun 25 '11 15:06

Armen Tsirunyan


3 Answers

You can do something like the following. I have commented the code to explain what each part is doing, but essentially its this:

  • Create a "manipulator" struct which stores some data in the stream using xalloc and iword.
  • Create a custom num_put facet which looks for your manipulator and applies the manipulation.

Here is the code...

Edit: Note that im not sure I handled the std::ios_base::internal flag correctly here - as I dont actually know what its for.

Edit 2: I found out what std::ios_base::internal is for, and updated the code to handle it.

Edit 3: Added a call to std::locacle::global to show how to make all the standard stream classes support the new stream manipulator by default, rather than having to imbue them.

#include <algorithm>
#include <cassert>
#include <climits>
#include <iomanip>
#include <iostream>
#include <locale>

namespace StreamManip {

// Define a base manipulator type, its what the built in stream manipulators
// do when they take parameters, only they return an opaque type.
struct BaseManip
{
    int mBase;

    BaseManip(int base) : mBase(base)
    {
        assert(base >= 2);
        assert(base <= 36);
    }

    static int getIWord()
    {
        // call xalloc once to get an index at which we can store data for this
        // manipulator.
        static int iw = std::ios_base::xalloc();
        return iw;
    }

    void apply(std::ostream& os) const
    {
        // store the base value in the manipulator.
        os.iword(getIWord()) = mBase;
    }
};

// We need this so we can apply our custom stream manipulator to the stream.
std::ostream& operator<<(std::ostream& os, const BaseManip& bm)
{
    bm.apply(os);
    return os;
}

// convience function, so we can do std::cout << base(16) << 100;
BaseManip base(int b)
{
    return BaseManip(b);
}

// A custom number output facet.  These are used by the std::locale code in
// streams.  The num_put facet handles the output of numberic values as characters
// in the stream.  Here we create one that knows about our custom manipulator.
struct BaseNumPut : std::num_put<char>
{
    // These absVal functions are needed as std::abs doesnt support 
    // unsigned types, but the templated doPutHelper works on signed and
    // unsigned types.
    unsigned long int absVal(unsigned long int a) const
    {
        return a;
    }

    unsigned long long int absVal(unsigned long long int a) const
    {
        return a;
    }

    template <class NumType>
    NumType absVal(NumType a) const
    {
        return std::abs(a);
    }

    template <class NumType>
    iter_type doPutHelper(iter_type out, std::ios_base& str, char_type fill, NumType val) const
    {
        // Read the value stored in our xalloc location.
        const int base = str.iword(BaseManip::getIWord());

        // we only want this manipulator to affect the next numeric value, so
        // reset its value.
        str.iword(BaseManip::getIWord()) = 0;

        // normal number output, use the built in putter.
        if (base == 0 || base == 10)
        {
            return std::num_put<char>::do_put(out, str, fill, val);
        }

        // We want to conver the base, so do it and output.
        // Base conversion code lifted from Nawaz's answer

        int digits[CHAR_BIT * sizeof(NumType)];
        int i = 0;
        NumType tempVal = absVal(val);

        while (tempVal != 0)
        {
            digits[i++] = tempVal % base;
            tempVal /= base;
        }

        // Get the format flags.
        const std::ios_base::fmtflags flags = str.flags();

        // Add the padding if needs by (i.e. they have used std::setw).
        // Only applies if we are right aligned, or none specified.
        if (flags & std::ios_base::right || 
            !(flags & std::ios_base::internal || flags & std::ios_base::left))
        {
            std::fill_n(out, str.width() - i, fill);
        }

        if (val < 0)
        {
            *out++ = '-';
        }

        // Handle the internal adjustment flag.
        if (flags & std::ios_base::internal)
        {
            std::fill_n(out, str.width() - i, fill);
        }

        char digitCharLc[] = "0123456789abcdefghijklmnopqrstuvwxyz";
        char digitCharUc[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";

        const char *digitChar = (str.flags() & std::ios_base::uppercase)
            ? digitCharUc
            : digitCharLc;

        while (i)
        {
            // out is an iterator that accepts characters
            *out++ = digitChar[digits[--i]];
        }

        // Add the padding if needs by (i.e. they have used std::setw).
        // Only applies if we are left aligned.
        if (str.flags() & std::ios_base::left)
        {
            std::fill_n(out, str.width() - i, fill);
        }

        // clear the width
        str.width(0);

        return out;
    }

    // Overrides for the virtual do_put member functions.

    iter_type do_put(iter_type out, std::ios_base& str, char_type fill, long val) const
    {
        return doPutHelper(out, str, fill, val);
    }

    iter_type do_put(iter_type out, std::ios_base& str, char_type fill, unsigned long val) const
    {
        return doPutHelper(out, str, fill, val);
    }
};

} // namespace StreamManip

int main()
{
    // Create a local the uses our custom num_put
    std::locale myLocale(std::locale(), new StreamManip::BaseNumPut());

    // Set our locacle to the global one used by default in all streams created 
    // from here on in.  Any streams created in this app will now support the
    // StreamManip::base modifier.
    std::locale::global(myLocale);

    // imbue std::cout, so it uses are custom local.
    std::cout.imbue(myLocale);
    std::cerr.imbue(myLocale);

    // Output some stuff.
    std::cout << std::setw(50) << StreamManip::base(2) << std::internal << -255 << std::endl;
    std::cout << StreamManip::base(4) << 255 << std::endl;
    std::cout << StreamManip::base(8) << 255 << std::endl;
    std::cout << StreamManip::base(10) << 255 << std::endl;
    std::cout << std::uppercase << StreamManip::base(16) << 255 << std::endl;

    return 0;
}
like image 124
Node Avatar answered Oct 18 '22 16:10

Node


Custom manipulators are indeed possible. See for example this question. I'm not familiar with any specific one for universal bases.

like image 2
Assaf Lavie Avatar answered Oct 18 '22 14:10

Assaf Lavie


You really have two separate problems. The one I think you're asking about is entirely solvable. The other, unfortunately, is rather less so.

Allocating and using some space in the stream to hold some stream state is a problem that was foreseen. Streams have a couple of members (xalloc, iword, pword) that let you allocate a spot in an array in the stream, and read/write data there. As such, the stream manipulator itself is entirely possible. You'd basically use xalloc to allocate a spot in the stream's array to hold the current base, to be used by the insertion operator when it converts a number.

The problem for which I don't see a solution is rather simpler: the standard library already provides an operator<< to insert an int into a stream, and it obviously does not know about your hypothetical data to hold the base for a conversion. You can't overload that, because it would need exactly the same signature as the existing one, so your overload would be ambiguous.

The overloads for int, short, etc., however, are overloaded member functions. I guess if you wanted to badly enough, you could get by with using a template to overload operator<<. If I recall correctly, that would be preferred over even an exact match with a non-template function as the library provides. You'd still be breaking the rules, but if you put such a template in namespace std, there's at least some chance that it would work.

like image 2
Jerry Coffin Avatar answered Oct 18 '22 14:10

Jerry Coffin