Ответ 1
Выполнение всего этого немного связано. Чтобы сделать его понятным, я начну с основных вещей: использование пользовательских флагов форматирования для пользовательских типов. Пользовательское форматирование целых чисел будет следовать ниже.
Классы IOStream получают [косвенно] из std::ios_base
, который предоставляет два хранилища для данных: std::ios_base::iword()
и std::ios_base::pword()
для int
и void*
соответственно. Сохранение выделенной памяти, хранящейся в std::ios_base::pword()
, является нетривиальным и, к счастью, не требуется для этого относительно простого варианта использования. Чтобы использовать эти функции, которые возвращают ссылку не на const
на соответствующий тип, вы обычно выделяете индекс с помощью std::ios_base::xalloc()
один раз в своей программе и используете его всякий раз, когда вам нужно получить доступ к своим пользовательским флагам форматирования. Когда вы получаете доступ к значению с помощью iword()
или pword()
, изначально он будет инициализирован нолем. Чтобы свести дело, вот небольшая программа, демонстрирующая это:
#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';
}
Теперь int
как 4
не является определяющим пользователем типом, и для них уже существует оператор вывода. К счастью, вы можете настроить способ форматирования целочисленных чисел с использованием граней, более конкретно используя std::num_put<char>
. Теперь для этого вам нужно выполнить несколько шагов:
- Вывести класс из
std::num_put<char>
и переопределить членыdo_put()
, которым вы хотите дать специализированное поведение. - Создайте объект
std::locale
, используя вновь созданный грань. -
std::ios_base::imbue()
поток с новымstd::locale
.
Чтобы сделать пользователя более приятным, вы можете создать новый std::locale
с подходящей гранью std::num_put<char>
при использовании манипулятора. Однако, прежде чем делать это, позвольте начать с создания подходящей грани:
#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';
}
Что является немного уродливым, так это, чтобы imbue()
пользовательский std::locale
использовать манипулятор custom
. Чтобы избавиться от этого, мы можем просто убедиться, что пользовательский фасет установлен в используемом std::locale
, а если нет, просто установите его при установке флага:
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;
}
Теперь вы должны также переопределить различные члены do_put()
для правильной работы с различными типами unsigned
и с long long
, но это остается как упражнение.