Вопрос

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

Это было полезно?

Решение

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


Эти трудности здесь заключаются в том, что:

  • Мы играем с временным 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() вызовет сброс ядра.

Вызывающий str(): Догадываюсь.


Собирая все это воедино.

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

Ссылки:

.

Другие советы

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

Обновить: значительное улучшение кода благодаря литб.

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

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

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

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

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

#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 ++.

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top