Pull to refresh

Comments 39

В принципе действительно лишний.
Но это соответствует прототипу:
ToStream(std::wostream& strm, const T& val)
Тогда для const char *, char его не хватает.
А чем это лучше, чем просто переопределить operator<<?
template<typename S, typename A, typename B>
S& operator<<(S& s, const std::pair<A,B>& p) {
return s << '(' << p.a << ',' << p.b << ')';
}
В данном примере особых отличий нет. Собственно изначально вывод в лог и был так реализован…
Потом начались проблемы.

В реальном коде (см. ссылки в конце поста) ToString помещен в namespace. Это было сделано для избавления основного пространства имен от «мусора».

operator<<() придется перегружать в основном (глобальном) пространстве имен. Можно перегрузить и в отдельном namespace, но тогда получим кучу проблем потеряем удобство, например с STL, т.к. перегружать в namespace std нельзя (точнее это можно сделать, но, как правило, за это бьют ногами это чреревато проблемами в дальнейшем), а перегрузка в своем namespace не даст преимуществ — мы будем вынуждены явно указывать namespace (что выглядит страшновато — типа MyNamespace::operator<<(s, p) ) или использовать директиву using (засоряя глобальный namespace).

Если есть желание (и время), то попробуйте переписать мой код с перегрузкой операторов вывода и протестируйте на примерах. Если честно, то у меня не хватило терпения (или знаний/опыта) бороться с кучей ошибок, которые при этом вылезают (уж больно много перегрузок operator<<).

Опять-же, это лишь мой собственный опыт. Мне показалось, что это решение кому-то может пригодиться.
PS: Вроде говорили, что на Хабре новичков ногами не бьют (;
А что плохого в попадании operator<< в глобальное пространство имен?
Переменные и локально используемые классы это да, плохо, так как внезапно могут использоваться снаружи. А тип аргумента operator<< итак уже определен в каком-то пространстве имен и конфликта не будет. Проблем с различными подстановщиками кода тоже не вижу.
> А что плохого в попадании operator<< в глобальное пространство имен?

Ничего особо плохого нет (кроме засорения глобального пространства имен).
С другой стороны, если у какого-то типа T есть оператор вывода в поток, то он подхватится моей функцией (<IsOutStreamable<T, wchar_t>::value даст нам «истину»).
эх :) «You like C++ because you’re only using 20% of it. And that’s fine, everyone only uses 20% of C++, the problem is that everyone uses a different 20%»
Б. Страуструп сказал фразу, с которой сложно не согласиться:

I have always wished for my computer to be as easy to use as my telephone; my wish has come true because I can no longer figure out how to use my telephone.

В вольном переводе это звучит так:

Я всегда мечтал, чтобы пользоваться компьютером было так-же легко, как и телефоном; моя мечта сбылась — теперь я никак не могу понять как пользоваться моим телефоном.
> простите за тафтологию
лучше за орфографию прощения попросите :)
> лучше за орфографию прощения попросите :)

Прошу прощения за орфографию — исправил.

Кстати, я не одинок в своих заблужденях — согласно Гуглу:
— about 101,000 for тафтологию
— about 57,200 for тафтология

хотя это слабое утешение ;)…
Без использования буста решение задачи было бы намного веселей :). Однажды приходилось делать подобное, основной сложностью были type traits. Кстати к вопросу зачем это (вывод в поток) может понадобится, кроме логгирования — реализация простейших data access layer-ов (ORM).
type traits без буста это точно не просто.
собственно самое сложное, что используется из буста в моем коде, это BOOST_MPL_HAS_XXX_TRAIT_DEF.
Остальное реализуется проще:
— вместо boost::type_traits::no_type и boost::type_traits::yes_type вполне можно использовать классические char и char[2] (от Александреску)
— вместо enable_if аналог от того-же Александреску (если не ошибаюсь, у него есть select с помощью которого можно реализовать enable_if)

Только вот вопрос — а зачем без буста?
По-моему, boost — отличная библиотека (если не заглядывать в исходники ;) ).
Везде, где я работал буст — стандартная библиотека.
Я может чего не понял, всегда делал вывод контейнеров в одну строку

std::copy(r.begin(), r.end(), std::ostream_iterator(std::cout, "\n"));
UFO landed and left these words here
Мне кажется тут человек с Java-way пытается поднять на дыбы плюсы.
дело Александреску живет и побеждает.
> std::copy(r.begin(), r.end(), std::ostream_iterator(std::cout, "\n"));

Отличный пример, знакомый еще из классического труда господина Страуструпа.

Если Вы обратили внимание на начало поста, то там я написал для чего я использую данный код — для вывода в лог.
В прикладном коде я использую макрос _VAR (определение макроса смотрите в посте).

сам вывод в лог выглядит примерно так (в реальном коде выводится не в стандартный поток вывода, а в логгер, но в данном случае это не важно):

int i = 0;
int n = 10;
std::vector v;
std::map<int, int> m;
std::map<std::wstring, std::vector > msv;

std::cout << _VAR(i) << _VAR(n) << _VAR(v) << _VAR(m) << _VAR(msv);

Лично я вижу 2 основных преимущества:
1. Однообразность вывода — нет заморочек на тип переменной.
2. При изменении типа переменной, код для вывода в лог не меняется (возможно, что изменится выводимый текст, но код останется прежний).

PS: для map (m и msv) приведенный Вами код не скомпилируется, все равно придется писать что-то более сложное…
Сделайте подсветку кода, читать невозможно. Можно воспользоваться тулзой для подсветки: tohtml.com/cpp/
не работает оно на Хабре. не-ра-бо-та-ет. Сами попробуйте.
Да, поспешил. Как вариант, можно воспользоватся подсветкой для C# из хабраредактора:
// Определяем has_iterator и т.д.
BOOST_MPL_HAS_XXX_TRAIT_DEF(iterator);
BOOST_MPL_HAS_XXX_TRAIT_DEF(const_iterator);
BOOST_MPL_HAS_XXX_TRAIT_DEF(value_type);

// Структура для теста "является ли тип стандартным контейнером (STL container)"
// Считаем, что тип это контейнер, если он содержит определение типов
// для iterator, const_iterator и value_type, но не является std::[w]string
template<typename T>
struct IsStdContainer
{
static const int value = boost::mpl::and_<
has_iterator<T>,
has_const_iterator<T>,
has_value_type<T>,
boost::mpl::not_<boost::is_same<T, std::string> >,
boost::mpl::not_<boost::is_same<T, std::wstring> >
>::value;
};


* This source code was highlighted with Source Code Highlighter.

www.bankinform.ru/HabraEditor/
Стоп! А что если у типа нет оператора вывода? Наткнулись на первоначальную проблему. Решение очевидно — нужно разрешить эту функцию только для типов, для которых оператор вывода в поток определен. В коде это выглядит так:

а я, наивный, почему-то всегда думал, что мне компилятор об этом скажет.
Без подсветки кода пост почти нечитабелен.
И отступов в начале строк
А есть в с++ что-то подобное функции var_dump/print_r в php?
Не хочу я разводить холивар, но процитирую из своего поста «нам ведь нужна производительность — иначе зачем мы полезли в C++»
на эту цитату я обратил внимание в самом начале, и вот к ней комментарий
«если нужна производительность, то нужно отказаться от потоков»

конечно, потоки — это круто, это стандарт программирования на С++, я не гуру в С++, но как-то мы с коллегами профайлили проект, и оказалось, что простой printf на 20% производительнее потоков.
С точки зрения производительности Вы совершенно правы — в зависимости от выводимых данных разница в производительности может отличаться даже в разы.
Более того, printf тоже не самый быстрый способ вывода.

Но есть несколько моментов:
1. Речь идет о логировании. Логи, как правило, записываются в файл(ы). Более того — для сброса логов часто применяют сброс на диск (flush) после каждой операции вывода (хорошие системы логирования позволяют включить/выключить это поведение). А дисковые операции настолько медленны, что замена потоков на printf не даст никакой ощутимой разницы в производительности.
2. Типобезопасность. Об этом уже сказано не мало. Лично я избегаю использования [s|n]printf. Причина простая — только на прошлой неделе я потратил около часа помогая коллеге найти причину падения. Причина оказалась в

printf("%d %s", i, s);

Вроде вполне так безобидно… однако он (коллега) поменял тип переменной i на int64. Мимо этой строчки проходили кучу раз, но на нее не подумали. Проблему нашел только когда посмотрел на ассемблерный код этой строки.
Вроде gcc умеет проверять строку формата printf на корректность (количество параметров и их тип), но компилятор от Майкрософт этого увы не делает…
Против printf есть ещё один аргумент. У нас, например, встречался примерно такой код:
struct String
{
// ...
operator const char*() { return something; }
// ...
};

String s;
printf("%s", s);

Работало по той счастливой случайности, что данные лежали первым мембером класса в виде NULL-terminated строки.
рискую получить минуса, но не могу не спросить, потому как нигде больше этого не встречал, и рискую остаться в неведении — а что означает L перед каждой символьной/строковой константой?
Это «широкая» строка/символ.
Т.е. то, что можно выводить в «широкий» поток (std::wostream).

Обычно для этих целей рекомендуют использовать макросы _T() или _TEXT().

В моем коде идет вывод в «широкий» поток, поэтому я явно использовал префикс L
о нет, они снова придумали qDebug %)

если по делу, то можно писать таким макаром:

typename<>
inline void ToStream<bool>(std::wostream& strm, const bool& val)
{
strm << ( val? L«true»: L«false» );
}

и спокойно размещать его в хидере
Only those users with full accounts are able to leave comments. Log in, please.