Подтвердить что ты не робот

Пользовательский манипулятор потока для потоковых целых чисел в любой базе

Я могу сделать объект std::ostream для вывода целых чисел в шестнадцатеричном виде, например

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

Есть ли какой-либо манипулятор, универсальный для всех баз? Что-то вроде

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

Если есть, то у меня больше нет вопросов. Если его нет, тогда я могу написать его? Разве это не потребует от меня доступа к частным деталям реализации std::ostream?

Обратите внимание, что я знаю, что могу написать функцию, которая принимает число и преобразует его в строку, представляющую это число в любой базе. Или я могу использовать тот, который уже существует. Я спрашиваю о пользовательских манипуляторах потока - возможно ли это?

Заранее спасибо

4b9b3361

Ответ 1

Вы можете сделать что-то вроде следующего. Я прокомментировал код, чтобы объяснить, что делает каждая часть, но по существу это:

  • Создайте структуру "манипулятор", которая хранит некоторые данные в потоке, используя xalloc и iword.
  • Создайте настраиваемый num_put фасет, который ищет ваш манипулятор и применяет манипуляции.

Вот код...

Изменить: Обратите внимание, что я не уверен, что правильно обработал флаг std::ios_base::internal здесь, так как я действительно не знаю, для чего он нужен.

Изменить 2: Я узнал, для чего std::ios_base::internal, и обновил код для его обработки.

Изменить 3: Добавлен вызов std::locacle::global, чтобы показать, как по умолчанию все стандартные классы потоков поддерживают новый манипулятор потока, вместо того, чтобы иметь imbue их.

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

Ответ 2

Пользовательские манипуляторы действительно возможны. См. Например этот вопрос. Я не знаком с какой-либо конкретной для универсальных баз.

Ответ 3

У вас действительно есть две отдельные проблемы. Тот, о котором я думаю, вы спрашиваете, полностью разрешима. Другой, к сожалению, в меньшей степени.

Выделение и использование некоторого пространства в потоке для хранения некоторого состояния потока - это проблема, которая была предусмотрена. Потоки имеют пару членов (xalloc, iword, pword), которые позволяют выделять пятно в массиве в потоке и читать/записывать данные там. Таким образом, сам манипулятор потока вполне возможен. Вы в основном используете xalloc для выделения пятна в массиве потоков для хранения текущей базы, которая будет использоваться оператором вставки при преобразовании числа.

Проблема, для которой я не вижу решения, довольно проста: стандартная библиотека уже предоставляет operator<<, чтобы вставить int в поток, и, очевидно, она не знает о ваших гипотетических данных для хранения базы для преобразования. Вы не можете перегрузить это, потому что ему потребуется точно такая же подпись, как и существующая, поэтому ваша перегрузка будет неоднозначной.

Перегрузки для int, short и т.д., однако, являются перегруженными функциями-членами. Думаю, если бы вы хотели достаточно плохо, вы могли бы воспользоваться шаблоном для перегрузки operator<<. Если я правильно помню, это было бы предпочтительнее даже в точном совпадении с функцией без шаблона, как предоставляет библиотека. Вы все равно нарушаете правила, но если вы разместите такой шаблон в пространстве имен std, то, по крайней мере, есть вероятность, что он сработает.

Ответ 4

Я попытался написать код и его работу с некоторыми ограничениями. Его не потоковый манипулятор как таковой, как это просто невозможно, о чем свидетельствуют другие (особенно @Jerry).

Вот мой код:

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

И это тестовый код:

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

Вывод:

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

Демо-версия онлайн: http://www.ideone.com/BWhW5

Ограничения:

  • База не может быть изменена дважды. Таким образом, это будет ошибкой:

    std::cout << base(4) << 879 << base(8) << 9878 ; //error
    
  • Другой манипулятор не может использоваться после использования base:

    std::cout << base(4) << 879 << std::hex << 9878 ; //error
    std::cout << std::hex << 879 << base(8) << 9878 ; //ok
    
  • std::endl не может использоваться после использования base:

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

Ответ 5

Я не думаю, что синтаксис возможен для произвольных потоков (используя манипулятор, @gigantt связывает ответ, который показывает некоторые альтернативные решения без манипулятора). Стандартные манипуляторы просто устанавливают параметры, которые реализованы внутри потока.

OTOH, вы могли бы сделать этот синтаксис:

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

Где base - объект, предоставляющий оператор вставки потока (нет необходимости возвращать временный string).