Макрос формата C++ / встроенный острингстрим
Я пытаюсь написать макрос, который позволил бы мне сделать что-то вроде: FORMAT(a << "b" << c << d)
, и результатом будет строка - то же самое, что создание потока ostring, вставка a...d
и возвращаясь .str()
, Что-то вроде:
string f(){
ostringstream o;
o << a << "b" << c << d;
return o.str()
}
По существу, FORMAT(a << "b" << c << d) == f()
,
Сначала я попробовал:
1: #define FORMAT(items) \
((std::ostringstream&)(std::ostringstream() << items)).str()
Если самый первый элемент является строкой C (const char *
), он напечатает адрес строки в шестнадцатеричном виде, а следующие элементы будут напечатаны нормально. Если самый первый элемент std::string
, он не скомпилируется (нет соответствующего оператора <<
).
Это:
2: #define FORMAT(items) \
((std::ostringstream&)(std::ostringstream() << 0 << '\b' << items)).str()
дает то, что кажется правильным выводом, но 0
а также \b
присутствуют в строке, конечно.
Кажется, что следующее работает, но компилируется с предупреждениями (принимая временный адрес):
3: #define FORMAT(items) \
((std::ostringstream&)(*((std::ostream*)(&std::ostringstream())) << items)).str()
Кто-нибудь знает, почему 1 печатает адрес c-строки и не компилируется с std::string
? Разве 1 и 3 по сути не одинаковы?
Я подозреваю, что C++0x variadic шаблоны сделают format(a, "b", c, d)
возможный. Но есть ли способ решить это сейчас?
7 ответов
Вы все уже в значительной степени прибили это. Но это немного сложно следовать. Итак, позвольте мне попытаться обобщить то, что вы сказали...
Вот трудности вот в чем:
Мы играем с временным
ostringstream
объект, поэтому взятие адресов противопоказано.Поскольку это временно, мы не можем тривиально преобразовать в
ostream
объект через кастинг.И конструктор [очевидно] и
str()
классostringstream
методы. (Да, нам нужно использовать.str()
, С использованиемostringstream
объект непосредственно будет вызыватьios::operator void*()
, возвращая хорошее / плохое значение, похожее на указатель, а не строковый объект.)operator<<(...)
существует как наследственноеostream
методы и глобальные функции. Во всех случаях он возвращаетostream&
ссылка.Выбор здесь для
ostringstream()<<"foo"
являются унаследованным методомostream::operator<<(void* )
и глобальная функцияoperator<<(ostream&,const char* )
, Наследственныйostream::operator<<(void* )
выигрывает, потому что мы не можем преобразовать вostream
ссылка на объект для вызова глобальной функции. [ Слава Коппро!]
Итак, чтобы осуществить это, нам нужно:
- Выделить временный
ostringstream
, - Преобразовать его в
ostream
, - Добавить данные.
- Преобразовать его обратно в
ostringstream
, - И ссылаться
str()
,
Выделяя: ostringstream()
,
Конвертация: Есть несколько вариантов. Другие предложили:
ostringstream() << std::string() // Kudos to *David Norman*
ostringstream() << std::dec // Kudos to *cadabra*
Или мы могли бы использовать:
ostringstream() . seekp( 0, ios_base::cur )
ostringstream() . write( "", 0 )
ostringstream() . flush()
ostringstream() << flush
ostringstream() << nounitbuf
ostringstream() << unitbuf
ostringstream() << noshowpos
- Или любой другой стандартный манипулятор. [
#include <iomanip>
] Ссылка: см. "Вставка данных в формате" на 1/3 пути вниз на этой веб-странице.
Мы не можем использовать:
operator<<( ostringstream(), "" )
(ostream &) ostringstream()
Добавление: прямо сейчас.
Преобразование обратно: мы могли бы просто использовать (ostringstream&)
, Но dynamic_cast
было бы безопаснее. В маловероятном случае dynamic_cast
возвращенный NULL
(это не должно), следующее .str()
вызовет coredump.
Вызов str()
: Угадай
Собираем все вместе.
#define FORMAT(ITEMS) \
( ( dynamic_cast<ostringstream &> ( \
ostringstream() . seekp( 0, ios_base::cur ) << ITEMS ) \
) . str() )
Рекомендации:
- Библиотека IOstream
ostringstream
ostream::operator<<()
- Учебник по кастингу
- Wiki: Тип кастинга
,
Вот что я использую. Все это вписывается в одно определение класса в заголовочном файле.
обновление: значительное улучшение кода благодаря Johannes Schaub - litb.
// makestring.h:
class MakeString
{
public:
std::stringstream stream;
operator std::string() const { return stream.str(); }
template<class T>
MakeString& operator<<(T const& VAR) { stream << VAR; return *this; }
};
Вот как это используется:
string myString = MakeString() << a << "b" << c << d;
Проблема, с которой вы столкнулись, связана с тем, что operator << (ostream&, char*)
не является членом ostream, и ваш временный экземпляр ostream не может связываться сconst
ссылка. Вместо этого он выбирает void*
перегрузка, которая является членом ostream, и, следовательно, не имеет этого ограничения.
Лучшее (но не самое простое и не самое элегантное из всех возможных представлений!) Было бы использовать препроцессор Boost для генерации большого числа перегрузок функций, каждый из которых был спроектирован для большого числа объектов (включения были опущены и предполагали, что using namespace std;
):
#define MAKE_OUTPUT(z, n, data) \
BOOST_PP_TUPLE_ELEM(2, 0, data) << BOOST_PP_CAT(BOOST_PP_TUPLE_ELEM(2, 1, data), n);
#define MAKE_FORMAT(z, n, data) \
template <BOOST_PP_ENUM_PARAMS_Z(z, BOOST_PP_INC(n), typename T)> \
inline string format(BOOST_PP_ENUM_BINARY_PARAMS_Z(z, BOOST_PP_INC(n), T, p)) \
{ \
ostringstream s; \
BOOST_PP_REPEAT_##z(z, n, MAKE_OUTPUT, (s, p)); \
return s.str(); \
}
Работать точно не гарантировано (написал без тестирования), но это в основном идея. Вы тогда звоните BOOST_PP_REPEAT(N, MAKE_FORMAT, ())
создать ряд функций, принимающих до N параметров, которые будут форматировать вашу строку так, как вы хотите (замените N целым числом выбора. Более высокие значения могут отрицательно повлиять на время компиляции). Этого должно быть достаточно, пока вы не получите компилятор с переменными шаблонами. Вы должны прочитать документацию по препроцессору boost, он обладает очень мощными возможностями для подобных вещей. (вы можете впоследствии #undef
макросы, после вызова BOOST_PP_REPEAT
вызов для генерации функций)
Вот ответ типа cadabra, который не связывается с состоянием ostream:
#define FORMAT(items) static_cast<std::ostringstream &>((std::ostringstream() << std::string() << items)).str()
Я полагаю, что первый абзац ответа Коппро описывает, почему вещи ведут себя так.
Когда я принял решение mrree (помеченное как "предпочтительный", прекрасно объясненное и отлично работающее для G++), я столкнулся с проблемами с MSVC++: все строки, созданные с помощью этого макроса, оказались пустыми.
Через несколько часов (и много почесывая мою голову и задавая здесь "перезагруженный" вопрос) позже, я обнаружил, что преступником был вызов seekp(). Я не уверен, что MSVC++ делает по-другому с этим, но замена
ostringstream().seekp( 0, ios_base::cur )
с кадаброй
ostringstream() << std::dec
работает и для MSVC++.
Вот рабочее решение:
#define FORMAT(items) \
((std::ostringstream&)(std::ostringstream() << std::dec << items)).str()
Я не совсем понимаю поведение первого аргумента.