Макрос формата С++/встроенный 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

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