Макрос формата C++ / встроенный 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 по сути то же самое?

Я подозреваю, что c++0x variadic шаблоны сделают format(a, "b", c, d) возможно. Но есть ли способ решить это?

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() вызовет coredump.

вызов str(): догадываться.


складывая все это вместе.

#define FORMAT(ITEMS)                                             \
  ( ( dynamic_cast<ostringstream &> (                             \
         ostringstream() . seekp( 0, ios_base::cur ) << ITEMS )   \
    ) . str() )

ссылки:

.


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

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

проблемы связана с тем, что 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 целым числом выбора. Более высокие значения могут негативно повлиять на время компиляции). Этого должно быть достаточно, пока вы не получите компилятор с variadic шаблонами. Вы должны прочитать boost Preprocessor documentation, он имеет очень мощные функции для таких вещей. (вы можете впоследствии #undef макросы, после вызова BOOST_PP_REPEAT вызов для генерации функций)


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

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

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


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

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

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


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

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

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

С

ostringstream() << std::dec

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


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