Макрос формата С++/встроенный ostringstream
Я пытаюсь написать макрос, который позволит мне сделать что-то вроде: 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 по существу одинаковыми?
Я подозреваю, что вариативные шаблоны С++ 0x сделают возможным format(a, "b", c, d)
. Но есть ли способ решить это сейчас?
Ответы
Ответ 1
Ты все это уже прибил. Но это немного сложно. Поэтому позвольте мне взять удар, чтобы подвести итог тому, что вы сказали...
Эти трудности заключаются в следующем:
-
Мы играем с временным объектом 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
для вызова глобальной функции. [Престижность к coppro!]
Итак, чтобы снять это, нам нужно:
- Выделите временный
ostringstream
.
- Преобразуйте его в
ostream
.
- Добавить данные.
- Преобразуйте его обратно в
ostringstream
.
- И вызовите
str()
.
Выделение: ostringstream()
.
Преобразование: Существует несколько вариантов. Другие предложили:
-
ostringstream() << std::string() // Kudos to *David Norman*
-
ostringstream() << std::dec // Kudos to *cadabra*
Или мы могли бы использовать:
Мы не можем использовать:
- <удаp >
operator<<( ostringstream(), "" )
удаp >
- <удаp >
(ostream &) ostringstream()
удаp >
Добавление: Прямо сейчас.
Преобразование назад: Мы могли бы просто использовать (ostringstream&)
. Но a dynamic_cast
будет более безопасным. В маловероятном случае dynamic_cast
возвращается NULL
(он не должен), следующий .str()
вызовет coredump.
Вызов str()
: Угадай.
Объединяя все это.
#define FORMAT(ITEMS) \
( ( dynamic_cast<ostringstream &> ( \
ostringstream() . seekp( 0, ios_base::cur ) << ITEMS ) \
) . str() )
Литература:
.
Ответ 2
Вот что я использую. Все это вписывается в одно опрятное определение класса в файле заголовка.
обновление: значительное улучшение кода благодаря 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;
Ответ 3
Проблема, с которой вы сталкиваетесь, связана с тем, что operator << (ostream&, char*)
не является членом ostream, а ваш временный экземпляр exstream не может привязываться к ссылке 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 целым выбором). Более высокие значения могут отрицательно повлиять на время компиляции). Этого достаточно, пока вы не получите компилятор с вариативными шаблонами. Вы должны прочитать документацию по препроцессору ускорения, у нее есть очень мощные функции для таких вещей. (впоследствии вы можете #undef
использовать макросы после вызова BOOST_PP_REPEAT
для генерации функций)
Ответ 4
Вот ответ, подобный кадабре, который не воюет с состоянием ostream:
#define FORMAT(items) static_cast<std::ostringstream &>((std::ostringstream() << std::string() << items)).str()
Я считаю, что первый абзац ответа coppro описывает, почему все так ведет себя.
Ответ 5
Здесь рабочее решение:
#define FORMAT(items) \
((std::ostringstream&)(std::ostringstream() << std::dec << items)).str()
Я не совсем понимаю поведение первого аргумента.
Ответ 6
Когда я принял решение mrree (тот, который был отмечен как "предпочтительный" ), тот красиво объяснил, и тот, который отлично работает для g++), я столкнулся с проблемами с MSVС++: все строки, построенные с этим макросом, оказались пустыми.
Часы (и много царапин на голове и вопрос "reloaded" ), я узнал, что вызов seekp() был виновником. Я не уверен, что MSVС++ делает по-другому с этим, но заменяя
ostringstream().seekp( 0, ios_base::cur )
с кадаброй
ostringstream() << std::dec
работает и для MSVС++.
Ответ 7
Почему бы просто не использовать функцию вместо макроса?