Макрос формата C ++ / встроенный острингстрим - PullRequest
14 голосов
/ 20 ноября 2008

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

Ответы [ 7 ]

20 голосов
/ 20 ноября 2008

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

обновление: значительное улучшение кода благодаря 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;
19 голосов
/ 20 ноября 2008

Вы все уже в значительной степени прибили это. Но это немного сложно следовать. Итак, позвольте мне попытаться обобщить то, что вы сказали ...


Вот какие трудности:

  • Мы играем с временным 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*

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

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

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

Ссылки

.

4 голосов
/ 20 ноября 2008

Проблема, с которой вы столкнулись, связана с тем, что operator << (ostream&, char*) не является членом ostream, и ваш временный экземпляр ostream не может связываться с не-1002 * ссылкой. Вместо этого он выбирает перегрузку 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 вызова для генерации функций)

2 голосов
/ 20 ноября 2008

Вот ответ типа cadabra, который не связывается с состоянием ostream:

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

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

1 голос
/ 30 января 2009

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

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

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

с кадаброй

ostringstream() << std::dec

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

1 голос
/ 20 ноября 2008

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

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

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

0 голосов
/ 20 ноября 2008

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

...