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

Я пытаюсь написать макрос, который позволил бы мне сделать что-то вроде: 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 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 ссылка на объект для вызова глобальной функции. [ Слава Коппро!]


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

  • Выделить временный 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() )

Рекомендации:

,

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

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

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

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

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

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

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

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

с кадаброй

ostringstream() << std::dec

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

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

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

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

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

Другие вопросы по тегам