Наследование std::ostream и форматирование вывода
От: Madkinder  
Дата: 25.03.09 18:26
Оценка:
Доброго времени суток, all.
Есть задача писать в поток древовидную структуру данных, каждый из узлов дерева представляет собой либо целое число, либо вещественное число, либо строку символов.

Проблема №1
Каждый из типов данных должен быть специально отформатирован. Для вещественного и целого типов форматирование можно порулить модификаторами dec, scientific, showpoint, setprecision и им подобным.

Все строки должны быть взяты в кавычки. Хотелось бы получить такой функционал посредством создания своего модификатора, что-то в роде:

std::cout << quotes("\"")
    << std::string("Hello world!") << std::endl
    << std::string("A test") << std::endl;


И на выходе получить

"Hello world!"
"A test"


Проблема №2
Т.к. выводить нужно древовидную структуру, то хотелось бы отбивать уровень дерева tab-ами. Опять же используя модификатор. setw и setfill не подходят, т.к заранее неизвестно, сколько символов будет в текстовом представлении выводимой переменной.

Первое решение, которое пришло в голову -- унаследоваться от std::ostream, добавить методы settablevel(int) и перегрузить ostream& operator<<(ostream&, const & std::string). Но как потом заставить это работать и с fstream и со stringstream, ведь они наследуют ostream, но ни как не мой самопальный класс.

Следующее, что подумалось -- boost::iostreams. Ранее с boost-ом сталкиваться не приходилось, и беглый осмотр iostreams не дал результатов.

Как решить проблемы-то? Уж больно не хочется руками форматировать...

Заранее спасибо.
Re: Наследование std::ostream и форматирование вывода
От: Sergey Россия  
Дата: 25.03.09 19:30
Оценка:
Здравствуйте, Madkinder, Вы писали:

M>Доброго времени суток, all.

M>Есть задача писать в поток древовидную структуру данных, каждый из узлов дерева представляет собой либо целое число, либо вещественное число, либо строку символов.

M>Проблема №1

M>Каждый из типов данных должен быть специально отформатирован. Для вещественного и целого типов форматирование можно порулить модификаторами dec, scientific, showpoint, setprecision и им подобным.

M>Все строки должны быть взяты в кавычки. Хотелось бы получить такой функционал посредством создания своего модификатора, что-то в роде:


Я в таких случаях обычно завожу вспомогательный тип — например
template <class T> struct quoted_t
{
    T const &v_;
    explicit quoted_t(T const &v) : v_(v) {}
};


для которого переопределяю оператор вывода, который и ставит кавычки, эскейпит запрещенные символы и т.д.. и вспомогательную шаблонную функцию
template<class T> quoted_t<T> quoted(T const& t)
{ return quoted_t<T>(t); }


Функция, как не трудно догадаться, нужна только для того чтобы не нужно было писать тип, которым инстанцируется quoted_t. Если закавычивать надо только строки, то шаблоны и функция она не нужны.

M>Проблема №2

M>Т.к. выводить нужно древовидную структуру, то хотелось бы отбивать уровень дерева tab-ами. Опять же используя модификатор. setw и setfill не подходят, т.к заранее неизвестно, сколько символов будет в текстовом представлении выводимой переменной.

M>Первое решение, которое пришло в голову -- унаследоваться от std::ostream, добавить методы settablevel(int) и перегрузить ostream& operator<<(ostream&, const & std::string). Но как потом заставить это работать и с fstream и со stringstream, ведь они наследуют ostream, но ни как не мой самопальный класс.


Тут вопрос — какой уровень автоматизма требуется. Если устраивает что-то вроде
ident i;
s << i << quoted("level 0") << endl
  << i << quoted("level 0 again") << endl
  << ++i << quoted("level 1") << endl
  << --i << quoted("level 0") << endl;


то это относительно несложно организовать. Если хочется что-то другое — опишите задачу подробнее.


M>Следующее, что подумалось -- boost::iostreams. Ранее с boost-ом сталкиваться не приходилось, и беглый осмотр iostreams не дал результатов.


Это вообще не про то.
Одним из 33 полных кавалеров ордена "За заслуги перед Отечеством" является Геннадий Хазанов.
Re[2]: Наследование std::ostream и форматирование вывода
От: Madkinder  
Дата: 26.03.09 09:24
Оценка:
Здравствуйте, Sergey, Вы писали:

M>>Проблема №1

M>>Все строки должны быть взяты в кавычки. Хотелось бы получить такой функционал посредством создания своего модификатора, что-то в роде:

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

S>Функция, как не трудно догадаться, нужна только для того чтобы не нужно было писать тип, которым инстанцируется quoted_t. Если закавычивать надо только строки, то шаблоны и функция она не нужны.

Кавычить нужно только строки, и да, внутренности нужно эскейпить. Ваш подход хорош, но для моего случая имеет пару недостатков:

  1. Слишком общий, такой гибкости мне не надо, т.к. обрабатывать нужно только строки. Ну, это просто исправить.
  2. Всё-таки используется дополнительный класс-обёртка, а его очень хотелось бы избежать. Слишком велик шанс того, что пользователь класса влепит std::string вместо quoted (а то и строковый литерал). Нельзя ли обойтись без дополнительного класса?

Покритикуйте, пожалуйста, упрощённую реализацию. Никаких граблей с неявным преобразованием типов не возникнет?
#include <iostream>
#include <string>

class quoted : public std::string {
        public:
                explicit quoted(const char * oString) : std::string(oString) { }
                explicit quoted(const std::string & oString) : std::string(oString) { }
};

std::ostream & operator<<(std::ostream & oStream, const quoted & oString) {
        using std::string;

        string s(oString.c_str());

        string::size_type i = static_cast<string::size_type>(0);

        while ((i = s.find('\"', i)) != string::npos) {
                s.insert(i, 1, '\\');
                i += 2;
        }

        oStream << "\"" << s << "\"";
        return oStream;
}


M>>Проблема №2


S>Тут вопрос — какой уровень автоматизма требуется.

Если быть точным, то хочется так:
std::fstream s("qwe.txt");

s << setindent(2) // Каждая последующая новая строка должна быть отбита 2мя tab-ами.
  << "test" << 123 << std::endl
  << "yet another test" << 567 << std::endl
  << setindent(1) // Каждая последующая новая строка должна быть отбита 1м tab-ом.
  << "test once again" << 890 << std::endl;


и на выходе:

        "test" 123
        "yet another test" 567
    "test once again" 890


M>>Следующее, что подумалось -- boost::iostreams. Ранее с boost-ом сталкиваться не приходилось, и беглый осмотр iostreams не дал результатов.

S>Это вообще не про то.

Да вот мне тоже так показалось.
Re[3]: Наследование std::ostream и форматирование вывода
От: Sergey Россия  
Дата: 26.03.09 10:05
Оценка:
"Madkinder" <8566@users.rsdn.ru> wrote in message news:3342774@news.rsdn.ru...

>

    >
  1. Слишком общий, такой гибкости мне не надо, т.к. обрабатывать нужно только строки. Ну, это просто исправить.
    >
  2. Всё-таки используется дополнительный класс-обёртка, а его очень хотелось бы избежать. Слишком велик шанс того, что пользователь класса влепит std::string вместо quoted (а то и строковый литерал). Нельзя ли обойтись без дополнительного класса?
    >

Ну тогда только с наследованием, imho. Правда, как мне кажется, проблем при этом не меньше — все равно кто-нибудь может написать

string val = "_value_";
s << "prefix" << val << "suffix";

ожидая получить на выходе "prefix_value_suffix". Ну и до кучи — предположим, как-то удалось решить вашу проблему без наследования и введения лишнего типа, т.е. все строки выводятся во все стримы закавыченными. Вы абсолютно уверены, что у вас в программе не возникнет ситуация, когда в какой-то стрим не понадобится вывести строку без кавычек? Это я к тому, что как-то отделять обычный вывод строк в потоки от "продвинутого" обычно все-таки требуется.

> Покритикуйте, пожалуйста, упрощённую реализацию. Никаких граблей с неявным преобразованием типов не возникнет?

>
> #include <iostream>
> #include <string>
> 
> class quoted : public std::string {
>        public:
>                explicit quoted(const char * oString) : std::string(oString) { }
>                explicit quoted(const std::string & oString) : std::string(oString) { }
> };
> 
> std::ostream & operator<<(std::ostream & oStream, const quoted & oString) {
>        using std::string;
> 
>        string s(oString.c_str());
> 
>        string::size_type i = static_cast<string::size_type>(0);
> 
>        while ((i = s.find('\"', i)) != string::npos) {
>                s.insert(i, 1, '\\');
>                i += 2;
>        }
> 
>        oStream << "\"" << s << "\"";
>        return oStream;
> }
>


Наследование от строки — зря, лучше сделать ее членом, копия которая называется s — тоже на мой взгляд лишняя, быстрее будет посимвольно выводить. Больше никаких проблем на первый взгляд не вижу.

>

> M>>Проблема №2
>
> S>Тут вопрос — какой уровень автоматизма требуется.
> Если быть точным, то хочется так:
>
> std::fstream s("qwe.txt");
> 
> s << setindent(2) // Каждая последующая новая строка должна быть отбита 2мя tab-ами.
>  << "test" << 123 << std::endl
>  << "yet another test" << 567 << std::endl
>  << setindent(1) // Каждая последующая новая строка должна быть отбита 1м tab-ом.
>  << "test once again" << 890 << std::endl;
>

>
> и на выходе:
>
>
> "test" 123
> "yet another test" 567
> "test once again" 890
>


Если устроит вместо std::endl писать my::endl или скажем newline, то реализация будет тривиальной. Если все-таки хочется именно std::endl в сочетании именно со стандартными стримами, то лично я ничего посоветовать не могу, поскольку считаю подобные решения хождением по граблям и, соответственно, на практике проделывать такое не пробовал. Хотя через наследование или там фильтры из boost::iostreams наверное получится.
Posted via RSDN NNTP Server 2.1 beta
Одним из 33 полных кавалеров ордена "За заслуги перед Отечеством" является Геннадий Хазанов.
Re[4]: Наследование std::ostream и форматирование вывода
От: Madkinder  
Дата: 26.03.09 11:32
Оценка:
Здравствуйте, Sergey, Вы писали:

S>Ну тогда только с наследованием, imho. Правда, как мне кажется, проблем при этом не меньше — все равно кто-нибудь может написать

string val = "_value_";
s << "prefix" << val << "suffix";

S>Вы абсолютно уверены, что у вас в программе не возникнет ситуация, когда в какой-то стрим не понадобится вывести строку без кавычек?
Это я уже понял ещё при написании прошлого ответа на Ваш пост. Сейчас уже интересна сама возможность реализовать подобное. Даже с наследованием ума не приложу, как можно добиться результата. Ведь наследоваться-то нужно от ostream, а получить функционал в классах stringstream и fstream.

Остановлюсь, пожалуй, на варианте с классом quoted.

S>Наследование от строки — зря, лучше сделать ее членом, копия которая называется s — тоже на мой взгляд лишняя, быстрее будет посимвольно выводить. Больше никаких проблем на первый взгляд не вижу.

Чем чревато наследование от строки? Это запрещено стандартом или где-то могут вылезти другие подводные грабли?

S>Если устроит вместо std::endl писать my::endl или скажем newline, то реализация будет тривиальной.


Возможно я чего-то недопонимаю, ведь my::endl будет добавляться после самой строки, в то время, как табы нужно вставлять до неё. Можно ли пример реализации или более подробное описание подхода?


Спасибо за потраченное время.
Re[5]: Наследование std::ostream и форматирование вывода
От: Sergey Россия  
Дата: 26.03.09 13:22
Оценка:
"Madkinder" <8566@users.rsdn.ru> wrote in message news:3343032@news.rsdn.ru...

> S>Ну тогда только с наследованием, imho. Правда, как мне кажется, проблем при этом не меньше — все равно кто-нибудь может написать

>
> string val = "_value_";
> s << "prefix" << val << "suffix";
>

> S>Вы абсолютно уверены, что у вас в программе не возникнет ситуация, когда в какой-то стрим не понадобится вывести строку без кавычек?
> Это я уже понял ещё при написании прошлого ответа на Ваш пост. Сейчас уже интересна сама возможность реализовать подобное. Даже с наследованием ума не приложу, как можно добиться результата. Ведь наследоваться-то нужно от ostream, а получить функционал в классах stringstream и fstream.

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


> S>Наследование от строки — зря, лучше сделать ее членом, копия которая называется s — тоже на мой взгляд лишняя, быстрее будет посимвольно выводить. Больше никаких проблем на первый взгляд не вижу.

> Чем чревато наследование от строки? Это запрещено стандартом или где-то могут вылезти другие подводные грабли?

Да ни чем не чревато, чистая вкусовщина (если не рассматривать совсем уж экзотические варианты использования, приводящие к удаления вашей обертки через указатель на строку). Я просто всегда стараюсь использовать максимально слабое кунфу, особенно в библиотечном коде. А раз здесь не требуется, чтобы quoted вел себя как строка, то и не надо этого допускать. А то мало ли как пользователи этот факт используют, потом упаришься совместимость поддерживать.

> S>Если устроит вместо std::endl писать my::endl или скажем newline, то реализация будет тривиальной.

>
> Возможно я чего-то недопонимаю, ведь my::endl будет добавляться после самой строки, в то время, как табы нужно вставлять до неё. Можно ли пример реализации или более подробное описание подхода?

Ну вообще-то комментарий в setindent гласит — "Каждая последующая новая строка должна быть отбита N tab-ами". Соответственно, достаточно вставлять табы после начала строки в соответствии с текущей установкой setindent. Заводите самопальный фасет
struct current_tabbing : public std::locale::facet
{
    unsigned n_;
    current_tabbing(std::size_t refs, unsigned n) : std::locale::facet(refs), n_(n) {}
};


который setindent будет при необходимости добавлять к текущей локали


std::ostream& operator<<(ostream& s, const setindent& v)

{

    if (std::has_facet<current_tabbing>(s.getloc()))

        std::use_facet<current_tabbing>(s.getloc()).n_ = v.n_;

    else

    {

        current_tabbing *f = new current_tabbing(v.n_);

        std::locale l = std::locale(s.getloc(), f);

        s.imbue(l);

    }   

}


и из которого newline будет потом брать n_, чтобы узнать сколько табов выводить.

Хотя тут по-моему вообще имеет смысл уйти от стримов к какой-то вспомогательной обертке над стримами, которая и будет заниматься идентацией и закавычиванием строк.
Posted via RSDN NNTP Server 2.1 beta
Одним из 33 полных кавалеров ордена "За заслуги перед Отечеством" является Геннадий Хазанов.
Re[2]: Наследование std::ostream и форматирование вывода
От: Кодт Россия  
Дата: 26.03.09 14:27
Оценка:
Здравствуйте, Sergey, Вы писали:

S>Тут вопрос — какой уровень автоматизма требуется. Если устраивает что-то вроде

S>
S>ident i;
S>s << i << quoted("level 0") << endl
S>  << i << quoted("level 0 again") << endl
S>  << ++i << quoted("level 1") << endl
S>  << --i << quoted("level 0") << endl;
S>


unspecified behavior или просто бред.
Потому что порядок выполнения автоинкремента, автодекремента и обоих просто подстановок — в данном выражении не определён.

Если компилятор не захочет срезать углы и проинлайнить, то получим отступы 0,0,0,-1 (при передаче ident в operator<< по значению) или 0,0,0,0 (по ссылке).
А если срежет и проинлайнит — получим лотерею.
... << RSDN@Home 1.2.0 alpha 4 rev. 1111>>
Перекуём баги на фичи!
Re[3]: Наследование std::ostream и форматирование вывода
От: Кодт Россия  
Дата: 26.03.09 14:27
Оценка:
Здравствуйте, Madkinder, Вы писали:

M>Кавычить нужно только строки, и да, внутренности нужно эскейпить. Ваш подход хорош, но для моего случая имеет пару недостатков:


M>

    M>
  1. Слишком общий, такой гибкости мне не надо, т.к. обрабатывать нужно только строки. Ну, это просто исправить.
    M>
  2. Всё-таки используется дополнительный класс-обёртка, а его очень хотелось бы избежать. Слишком велик шанс того, что пользователь класса влепит std::string вместо quoted (а то и строковый литерал). Нельзя ли обойтись без дополнительного класса?
    M>

Доп.класса-обёртки не хочется, а наследоваться от стандартного — хочется?
Лучше уж пропатчить ostream. (Возможно, унаследоватся от него).
Тогда и строки туда можно будет передавать любые — хоть литералы. И с отступами всё что угодно делать.
... << RSDN@Home 1.2.0 alpha 4 rev. 1111>>
Перекуём баги на фичи!
Re: Наследование std::ostream и форматирование вывода
От: LaptevVV Россия  
Дата: 26.03.09 14:38
Оценка:
Здравствуйте, Madkinder, Вы писали:

M>Первое решение, которое пришло в голову -- унаследоваться от std::ostream, добавить методы settablevel(int) и перегрузить ostream& operator<<(ostream&, const & std::string). Но как потом заставить это работать и с fstream и со stringstream, ведь они наследуют ostream, но ни как не мой самопальный класс.

На эту тему посмотри книжку Татьяны Павловской И Юрия Щупака — сборник задач по ОО-программированию на С++ (название не точное), издательство Питер. Там Щупак как раз описывает наследование от стандартного ostream в целях справиться с русскими буквами.

По поводу написания манипулятора — это вполне можно сделать.
В книжке Эккеля есть — см пример сюда:

Написать манипулятор с аргументами несколько сложнее. Для создания манипулятора, совместимого по интерфейсу со стандартными, нужно разобраться в стандартном механизме реализации. Вместо этого можно написать некоторый класс, в котором определить operator<<. Брюс Эккель вслед за Джерри Шварцем, одним из разработчиков объектно-ориентированной библиотеки ввода/вывода, называет такие классы эффекторами. Например, требуется выводить целые значения в двоичном виде. Назовем наш класс binary (листинг 8.13).
Листинг 8.13. «Манипулятор» для двоичного вывода

class binary
{ unsigned long k;
  public:
    binary( unsigned long k ): k( k ) {}
    friend ostream& operator<<( ostream& os, const binary& t );
};
inline ostream& operator<<( ostream& os, const binary& t )
{   const unsigned long MAX = numeric_limits<unsigned long>::max();     // 1
    unsigned long bit = ~( MAX >> 1 );                                  // 2
    while( bit ) { os << ( t.k & bit ? '1' : '0' ); bit >>= 1; }
    return os;
}

Такой класс позволяет выводить целые в двоичном виде, например:
short a = 2;
cout << binary( a ) << endl;
Этот класс всегда выводит количество бит, содержащееся в unsigned long, но можно легко преобразовать этот класс в шаблон, который позволит выводить нужное количество бит для беззнаковых целых (листинг 8.14).
Листинг 8.14. Эффектор-шаблон
// опережающие объявления
template <class T> class binary;
template<class T> ostream& operator<<( ostream& os, const binary<T>& t );
// класс-шаблон
template <class T>
class binary
{   T k;
  public:
    binary( T k ): k( k ) {}
    friend ostream& operator<< <>( ostream& os, const binary<T>& t );
};
template<class T>
inline
ostream& operator<<( ostream& os, const binary<T>& t )
{   T MAX = numeric_limits<T>::max();
    T bit = ~( MAX >> 1 );
    while( bit ) { os << ( t.k & bit? '1': '0' ); bit >>= 1; }
    return os;
}

Использовать этот класс-шаблон в качестве манипулятора можно так:
short a = -2;
cout << binary<unsigned short>( a ) << endl;
cout << binary<unsigned char>( 128 )<< endl;
На экран выведется
1111111111111110
10000000
Как видим, на экране ровно столько бит, сколько занимают типы unsigned short и unsigned char. Заметим, что использовать в качестве аргумента шаблона знаковый тип нельзя — программа зациклится!

Хочешь быть счастливым — будь им!
Без булдырабыз!!!
Re[3]: Наследование std::ostream и форматирование вывода
От: Sergey Россия  
Дата: 26.03.09 14:41
Оценка: 60 (1)
" Кодт " <4783@users.rsdn.ru> wrote in message news:3343315@news.rsdn.ru...
> Здравствуйте, Sergey, Вы писали:
>
> S>Тут вопрос — какой уровень автоматизма требуется. Если устраивает что-то вроде
> S>
> S>ident i;
> S>s << i << quoted("level 0") << endl
> S>  << i << quoted("level 0 again") << endl
> S>  << ++i << quoted("level 1") << endl
> S>  << --i << quoted("level 0") << endl;
> S>

>
> unspecified behavior или просто бред.
> Потому что порядок выполнения автоинкремента, автодекремента и обоих просто подстановок — в данном выражении не определён.
>
> Если компилятор не захочет срезать углы и проинлайнить, то получим отступы 0,0,0,-1 (при передаче ident в operator<< по значению) или 0,0,0,0 (по ссылке).
> А если срежет и проинлайнит — получим лотерею.

Да ну. Реально ident устроен так (далее он называется indent_t):

 struct indent_t
 {
  struct incr
  {
   indent_t& src_;
   explicit incr(indent_t& src) : src_(src) {}
  };
  struct decr
  {
   indent_t& src_;
   explicit decr(indent_t& src) : src_(src) {}
  };
  int n_;
  explicit indent_t(int n = 1) : n_(n) {}
  incr operator++()
  { return incr(*this); }
  decr operator--()
  { return decr(*this); }
  indent_t operator+(int x) const
  { return indent_t(n_ + x); }
  indent_t operator-(int x) const
  { return indent_t(n_ - x); }
 };
 inline std::ostream& operator<<(std::ostream& os, indent_t const& v)
 {
  assert(v.n_ >= 0);
  for (int n = v.n_; n != 0; --n)
   os << "  ";
  return os;
 }
 inline std::ostream& operator<<(std::ostream& os, indent_t::incr v)
 {
  ++v.src_.n_;
  return os << v.src_;
 }
 inline std::ostream& operator<<(std::ostream& os, indent_t::decr v)
 {
  --v.src_.n_;
  return os << v.src_;
 }


В каком конкретно месте тут unspecified?
Posted via RSDN NNTP Server 2.1 beta
Одним из 33 полных кавалеров ордена "За заслуги перед Отечеством" является Геннадий Хазанов.
Re[4]: Наследование std::ostream и форматирование вывода
От: Кодт Россия  
Дата: 26.03.09 16:28
Оценка:
Здравствуйте, Sergey, Вы писали:

>> unspecified behavior или просто бред.

S>Да ну. Реально ident устроен так (далее он называется indent_t):
<>

Ну тогда конечно... Expression Templates, отложенная арифметика...
... << RSDN@Home 1.2.0 alpha 4 rev. 1111>>
Перекуём баги на фичи!
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.