开发者

Custom stream manipulator for streaming integers in any base

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?


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;
}


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


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.


I attempted to write a code, and its working with some limitations. Its not stream manipulator as such, as that is simply not possible, as pointed out by others (especially @Jerry).

Here is my code:

struct base
{
   mutable std::ostream *_out;
   int _value;

   base(int value=10) : _value(value) {}

   template<typename T>
   const base& operator << (const T & data) const
   {
        *_out << data;
        return *this;
   }
   const base& operator << (const int & data) const
   {
        switch(_value)
        {
            case 2:  
            case 4:  
            case 8:  return print(data);
            case 16: *_out << std::hex << data; break;
            default:  *_out << data; 
        }
        return *this;
   }
   const base & print(int data) const
   {
        int digits[CHAR_BIT * sizeof(int)], i = 0;
        while(data)
        {
             digits[i++] = data % _value;  
             data /= _value;
        }
        while(i) *_out << digits[--i] ;
        return *this;
   }
   friend const base& operator <<(std::ostream& out, const base& b)   
   {
       b._out = &out;
       return b;
   }
};

And this is the test code:

int main() {
   std::cout << base(2) << 255 <<", " << 54 << ", " << 20<< "\n";
   std::cout << base(4) << 255 <<", " << 54 << ", " << 20<< "\n";
   std::cout << base(8) << 255 <<", " << 54 << ", " << 20<< "\n";
   std::cout << base(16) << 255 <<", " << 54 << ", " << 20<< "\n";
}

Output:

11111111, 110110, 10100
3333, 312, 110
377, 66, 24
ff, 36, 14

Online demo : http://www.ideone.com/BWhW5

Limitations:

  • The base cannot be changed twice. So this would be an error:

    std::cout << base(4) << 879 << base(8) << 9878 ; //error
    
  • Other manipulator cannot be used after base is used:

    std::cout << base(4) << 879 << std::hex << 9878 ; //error
    std::cout << std::hex << 879 << base(8) << 9878 ; //ok
    
  • std::endl cannot be used after base is used:

    std::cout << base(4) << 879 << std::endl ; //error
    //that is why I used "\n" in the test code.
    


I don't think that syntax is possible for arbitrary streams (using a manipulator, @gigantt linked an answer that shows some alternative non-manipulator solutions). The standard manipulators merely set options that are implemented inside the stream.

OTOH, you could certainly make this syntax work:

std::cout << base(4, 20);

Where base is an object that provides a stream insertion operator (no need to return a temporary string).

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜