Макрос формата C ++ / встроенный ostringstream

Я пытаюсь написать макрос, который позволил бы мне сделать что-то вроде: 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) возможным. Но есть ли способ решить эту проблему сейчас?


person cadabra    schedule 19.11.2008    source источник


Ответы (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 для вызова глобальной функции. [Престижность coppro!]


Итак, чтобы это осуществить, нам необходимо:

  • Выделить временный ostringstream.
  • Преобразуйте его в ostream.
  • Добавить данные.
  • Преобразуйте его обратно в ostringstream.
  • И вызовите str().

Распределение: ostringstream().

Преобразование. Есть несколько вариантов. Другие предложили:

  • ostringstream() << std::string() // Kudos to *David Norman*
  • ostringstream() << std::dec // Kudos to *cadabra*

Или мы могли бы использовать:

Мы не можем использовать:

  • 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() )

Использованная литература:

.

person Mr.Ree    schedule 20.11.2008
comment
Разве в аргументе этого макроса не должно быть эллипса? - person einpoklum; 29.06.2021

Вот что я использую. Все это укладывается в одно аккуратное определение класса в файле заголовка.

обновление: значительное улучшение кода благодаря 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;
person e.James    schedule 19.11.2008
comment
Ваш второй пример даст вам указатель на временную строку, которая была уничтожена - удачи с этим. - person Mark Ransom; 20.11.2008
comment
Я попробовал эту часть, но она все равно не компилируется. Я удалил это из своего ответа! Спасибо за ваш вклад. - person e.James; 20.11.2008
comment
нет необходимости в макросе: поместите шаблон ‹class T› StringMaker & operator ‹< (T const & VAR) {Stream ‹* VAR; вернуть * это; } внутри определения вашего класса и удалите ref - person Johannes Schaub - litb; 20.11.2008
comment
@litb: Это намного элегантнее. Спасибо! Я соответственно обновил свой ответ - person e.James; 20.11.2008
comment
Хорошо сделано. Обратите внимание, что можно безопасно неявно создать строку в стиле C, при условии, что строка будет использоваться только на протяжении выражения. См. Тесно связанные работы dribeas здесь: stackoverflow.com/questions/469696/ - person j_random_hacker; 30.01.2009
comment
@j_random_hacker: Спасибо. Я обычно предпочитаю строковый класс строкам в стиле c, но хорошо знать, что я могу использовать их в тех случаях, когда холодная и суровая реальность мешает мне принимать желаемое за действительное :) - person e.James; 01.02.2009
comment
Я независимо создал точно такой же класс, почти дословно. Вы также можете добавить шаблон ‹typename T_Stream› T_Stream & operator ‹< (T_Stream & os, const MakeString & ts) {return os ‹* (std :: string) ts; } Таким образом, вы можете вставить его во что-нибудь еще, когда это будет сделано. Хотя я признаю, что это очень мало добавляет функциональности, поскольку вы уже могли явно привести к строке. - person Mark Borgerding; 18.06.2009
comment
@Mark Borgerding: Я попытался добавить этот шаблон, но когда я пытаюсь проверить его, у меня появляется экран, полный ошибок компилятора. Не могли бы вы рассказать, как это нужно делать? Вы всегда можете опубликовать ответ, если вам нужно показать код :) - person e.James; 19.06.2009

Проблема, с которой вы столкнулись, связана с тем, что 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 для генерации функций)

person coppro    schedule 19.11.2008
comment
Спасибо, это очень информативно. Я не особо использовал Boost, интересно посмотреть, что там есть. - person cadabra; 20.11.2008
comment
Потому что временные не могут связываться с неконстантными ссылками. Например, string& str = string("hello"); недействителен. Таким образом, перегрузка для char * (и многих других перегрузок) не выбрана. - person coppro; 20.11.2008
comment
Дэвид: ха. В оригинальном плакате специально упоминались вариативные шаблоны, которые воспроизводит это вопиющее злоупотребление препроцессором (хотя и не наполовину). - person coppro; 20.11.2008
comment
Не поймите меня неправильно: вы ответили на самую сложную часть вопроса. - person David Norman; 20.11.2008

Вот такой ответ, как у cadabra, который не влияет на состояние ostream:

#define FORMAT(items)     static_cast<std::ostringstream &>((std::ostringstream() << std::string() << items)).str()

Я считаю, что первый абзац ответа Coppro описывает, почему вещи так себя ведут.

person David Norman    schedule 19.11.2008
comment
Не компилируется на gcc 4.0.1. Вот тот, который не влияет на состояние, но копирует строку. #define FORMAT (items) \ ((std :: ostringstream &) (std :: ostringstream () ‹---------------- 0 ‹* items)). str (). substr (1) - person cadabra; 20.11.2008

Вот рабочее решение:

#define FORMAT(items)                                                   \
   ((std::ostringstream&)(std::ostringstream() << std::dec << items)).str()

Я не совсем понимаю поведение первого аргумента.

person cadabra    schedule 19.11.2008
comment
std :: dec указывает потоку отображать числа как десятичные или десятичные. - person Jere.Jones; 20.11.2008
comment
Он использует один из форматеров элементов, чтобы затем вернуть ссылку, которая может быть привязана к временной. std :: dec - хороший параметр для передачи, который не окажет побочного эффекта на вывод потока. - person coppro; 20.11.2008

Когда я взял решение mrree (помеченное как «предпочтительное», прекрасно объясненное и отлично работающее с G ++), я столкнулся с проблемами с MSVC ++: все строки, созданные с помощью этого макроса, оказались пустыми.

Через несколько часов (и многочасового почесывания головы и задавания вопроса "перезагрузить" здесь) я обнаружил, что виноват вызов seekp (). Я не уверен, что MSVC ++ делает по-другому с этим, но заменяя

ostringstream().seekp( 0, ios_base::cur )

с кадабрами

ostringstream() << std::dec

работает и для MSVC ++.

person DevSolar    schedule 30.01.2009

Почему бы просто не использовать функцию вместо макроса?

person Paul Nathan    schedule 19.11.2008
comment
потому что он хочет использовать синтаксис оператора вставки - person Mr Fooz; 20.11.2008
comment
Полагаю, я мог бы вручную написать N шаблонных функций, по одной для каждого количества аргументов, а затем использовать формат (a, b, c, d). Или используйте решение Coppro для их создания. Но ни то, ни другое не является красивым. - person cadabra; 20.11.2008