Я пытаюсь написать макрос, который позволил бы мне сделать что-то вроде: 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 сделают возможным использование format(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 целым числом по выбору. Более высокие значения могут отрицательно повлиять на время компиляции). Этого должно хватить, пока вы не получите компилятор с вариативными шаблонами. Вы должны прочитать документацию по препроцессору ускорения, в ней есть очень мощные функции для таких вещей. (впоследствии вы можете использовать макросы #undef после вызова вызова BOOST_PP_REPEAT для генерации функций)
Потому что временные не могут связываться с неконстантными ссылками. Например, string& str = string("hello"); недействителен. Таким образом, перегрузка для char * (и многих других перегрузок) не выбрана.
Дэвид: ха. В оригинальном плакате специально упоминались вариативные шаблоны, которые воспроизводит это вопиющее злоупотребление препроцессором (хотя и не наполовину).
Не поймите меня неправильно: вы ответили на самую сложную часть вопроса.
Вот рабочее решение:
#define FORMAT(items) \
((std::ostringstream&)(std::ostringstream() << std::dec << items)).str()
Я не совсем понимаю поведение первого аргумента.
std :: dec указывает потоку отображать числа как десятичные или десятичные.
Он использует один из форматеров элементов, чтобы затем вернуть ссылку, которая может быть привязана к временному объекту. std :: dec - хороший параметр для передачи, который не окажет побочного эффекта на вывод потока.
Почему бы просто не использовать функцию вместо макроса?
потому что он хочет использовать синтаксис оператора вставки
Полагаю, я мог бы вручную написать N шаблонных функций, по одной для каждого количества аргументов, а затем использовать формат (a, «b», c, d). Или используйте решение Coppro для их создания. Но ни то, ни другое не является красивым.
Вот такой ответ, как у cadabra, который не влияет на состояние ostream:
#define FORMAT(items) static_cast<std::ostringstream &>((std::ostringstream() << std::string() << items)).str()
Я считаю, что первый абзац ответа Coppro описывает, почему вещи так себя ведут.
Не компилируется на gcc 4.0.1. Вот тот, который не влияет на состояние, но копирует строку. #define FORMAT (items) \ ((std :: ostringstream &) (std :: ostringstream () << 0 << items)). str (). substr (1)
Вот что я использую. Все это укладывается в одно аккуратное определение класса в файле заголовка.
Обновить: - значительное улучшение кода благодаря литб.
// 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;Ваш второй пример даст вам указатель на временную строку, которая была уничтожена - удачи с этим.
Я попробовал эту часть, но она все равно не компилируется. Я удалил это из своего ответа! Спасибо за ваш вклад.
нет необходимости в макросе: поместите шаблон <class T> StringMaker & operator << (T const & VAR) {Stream << VAR; return * this; } внутри определения вашего класса и удалите "ref"
@litb: Это намного элегантнее. Спасибо! Я соответственно обновил свой ответ
Хорошо сделано. Обратите внимание, что является можно безопасно неявно создавать строку в стиле C, при условии, что строка будет использоваться только на протяжении выражения. См. Тесно связанные работы dribeas здесь: stackoverflow.com/questions/469696/…
@j_random_hacker: Спасибо. Я обычно предпочитаю строковый класс строкам в стиле c, но приятно знать, что я могу использовать их в те времена, когда холодная, суровая реальность мешает мне принимать желаемое за действительное :)
Я независимо создал точно такой же класс, почти дословно. Вы также можете добавить шаблон <typename T_Stream> T_Stream & operator << (T_Stream & os, const MakeString & ts) {return os << (std :: string) ts; } Таким образом, вы можете вставить его во что-нибудь еще, когда это будет сделано. Хотя я признаю, что это очень мало добавляет функциональности, поскольку вы уже могли явно привести к строке.
@Mark Borgerding: Я попытался добавить этот шаблон, но когда я пытаюсь его протестировать, мне выводится экран, полный ошибок компилятора. Не могли бы вы рассказать, как это нужно делать? Вы всегда можете опубликовать ответ, если вам нужно показать код :)
Вы все уже в значительной степени справились с этим. Но уследить за этим немного сложно. Так что позвольте мне резюмировать то, что вы сказали ...
Вот эти трудности:
Мы играем с временным объектом 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() << flushostringstream() << nounitbufostringstream() << unitbufostringstream() << 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() )
Использованная литература:
ostringstreamostream::operator<<().
Когда я взял решение mrree (помеченное как «предпочтительное», прекрасно объясненное и отлично работающее с G ++), я столкнулся с проблемами с MSVC++: все строки, созданные с помощью этого макроса, оказались пустыми.
Спустя несколько часов (и много того, чтобы чесать голову и задавать здесь вопрос "перезагрузить") я обнаружил, что виноват вызов seekp (). Я не уверен, что MSVC++ делает по-другому с этим, но заменяя
ostringstream().seekp( 0, ios_base::cur )
с кадабрами
ostringstream() << std::dec
работает и для MSVC++.
Спасибо, это очень информативно. Я не особо использовал Boost, интересно посмотреть, что там есть.