Макрос формата C ++ / встроенный ostringstream
-
08-07-2019 - |
Вопрос
Я пытаюсь написать макрос, который позволил бы мне сделать что-то вроде: FORMAT(a << "b" << c << d)
, и результатом будет строка - то же самое, что создать ostringstream , вставив 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 сделают format(a, "b", c, d)
возможно.Но есть ли способ решить это сейчас?
Решение
Вы все уже в значительной степени справились с этим.Но следовать этому немного сложно.Итак, позвольте мне попытаться резюмировать то, что вы сказали...
Эти трудности здесь заключаются в том, что:
Мы играем с временным
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()
вызовет сброс ядра.
Вызывающий str()
: Догадываюсь.
Собирая все это воедино.
#define FORMAT(ITEMS) \
( ( dynamic_cast<ostringstream &> ( \
ostringstream() . seekp( 0, ios_base::cur ) << ITEMS ) \
) . str() )
Ссылки:
- Библиотека IOstream
ostringstream
ostream::operator<<()
- Учебное пособие по приведению типов
- Вики:Тип Литья
.
Другие советы
Вот что я использую.Все это укладывается в одно аккуратное определение класса в заголовочном файле.
Обновить: значительное улучшение кода благодаря литб.
// 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 preprocessor, в ней есть очень мощные функции для подобных вещей.(впоследствии вы можете #undef
макросы, после вызова BOOST_PP_REPEAT
вызов для генерации функций)
Вот ответ, подобный ответу кадабры, который не влияет на состояние ostream:
#define FORMAT(items) static_cast<std::ostringstream &>((std::ostringstream() << std::string() << items)).str()
Я полагаю, что первый абзац ответа коппро описывает, почему вещи ведут себя таким образом.
Вот рабочее решение:
#define FORMAT(items) \
((std::ostringstream&)(std::ostringstream() << std::dec << items)).str()
Я не совсем понимаю поведение первого аргумента.
Когда я воспользовался решением mrree (помеченным как "предпочтительное", прекрасно объясненным и идеально работающим для G ++), я столкнулся с проблемами с MSVC ++:Все строки, созданные с помощью этого макроса, оказались пустыми.
Часов (и много чесал в затылке и задавал "перезагрузка" вопрос здесь) позже я узнал, что виновником был вызов seekp().Я не уверен, что MSVC ++ делает по-другому с этим, но замена
ostringstream().seekp( 0, ios_base::cur )
с кадаброй
ostringstream() << std::dec
работает и для MSVC ++.
Почему бы просто не использовать функцию вместо макроса?