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

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

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
15
0
7 528
7
Перейти к ответу Данный вопрос помечен как решенный

Ответы 7

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

Спасибо, это очень информативно. Я не особо использовал Boost, интересно посмотреть, что там есть.

cadabra 20.11.2008 01:45

Потому что временные не могут связываться с неконстантными ссылками. Например, string& str = string("hello"); недействителен. Таким образом, перегрузка для char * (и многих других перегрузок) не выбрана.

coppro 20.11.2008 02:42

Дэвид: ха. В оригинальном плакате специально упоминались вариативные шаблоны, которые воспроизводит это вопиющее злоупотребление препроцессором (хотя и не наполовину).

coppro 20.11.2008 02:44

Не поймите меня неправильно: вы ответили на самую сложную часть вопроса.

David Norman 20.11.2008 02:47

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

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

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

std :: dec указывает потоку отображать числа как десятичные или десятичные.

Jere.Jones 20.11.2008 03:30

Он использует один из форматеров элементов, чтобы затем вернуть ссылку, которая может быть привязана к временному объекту. std :: dec - хороший параметр для передачи, который не окажет побочного эффекта на вывод потока.

coppro 20.11.2008 04:32

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

потому что он хочет использовать синтаксис оператора вставки

Mr Fooz 20.11.2008 01:35

Полагаю, я мог бы вручную написать N шаблонных функций, по одной для каждого количества аргументов, а затем использовать формат (a, «b», c, d). Или используйте решение Coppro для их создания. Но ни то, ни другое не является красивым.

cadabra 20.11.2008 01:47

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

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

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

Не компилируется на gcc 4.0.1. Вот тот, который не влияет на состояние, но копирует строку. #define FORMAT (items) \ ((std :: ostringstream &) (std :: ostringstream () << 0 << items)). str (). substr (1)

cadabra 20.11.2008 01:53

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

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

// 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;

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

Mark Ransom 20.11.2008 02:14

Я попробовал эту часть, но она все равно не компилируется. Я удалил это из своего ответа! Спасибо за ваш вклад.

e.James 20.11.2008 02:54

нет необходимости в макросе: поместите шаблон <class T> StringMaker & operator << (T const & VAR) {Stream << VAR; return * this; } внутри определения вашего класса и удалите "ref"

Johannes Schaub - litb 20.11.2008 09:34

@litb: Это намного элегантнее. Спасибо! Я соответственно обновил свой ответ

e.James 20.11.2008 10:05

Хорошо сделано. Обратите внимание, что является можно безопасно неявно создавать строку в стиле C, при условии, что строка будет использоваться только на протяжении выражения. См. Тесно связанные работы dribeas здесь: stackoverflow.com/questions/469696/…

j_random_hacker 30.01.2009 06:27

@j_random_hacker: Спасибо. Я обычно предпочитаю строковый класс строкам в стиле c, но приятно знать, что я могу использовать их в те времена, когда холодная, суровая реальность мешает мне принимать желаемое за действительное :)

e.James 01.02.2009 03:17

Я независимо создал точно такой же класс, почти дословно. Вы также можете добавить шаблон <typename T_Stream> T_Stream & operator << (T_Stream & os, const MakeString & ts) {return os << (std :: string) ts; } Таким образом, вы можете вставить его во что-нибудь еще, когда это будет сделано. Хотя я признаю, что это очень мало добавляет функциональности, поскольку вы уже могли явно привести к строке.

Mark Borgerding 18.06.2009 19:45

@Mark Borgerding: Я попытался добавить этот шаблон, но когда я пытаюсь его протестировать, мне выводится экран, полный ошибок компилятора. Не могли бы вы рассказать, как это нужно делать? Вы всегда можете опубликовать ответ, если вам нужно показать код :)

e.James 19.06.2009 06:00
Ответ принят как подходящий

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


Вот эти трудности:

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

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

  • ostringstream() . seekp( 0, ios_base::cur )
  • ostringstream() . write( "", 0 )
  • ostringstream() . flush()
  • ostringstream() << flush
  • ostringstream() << nounitbuf
  • ostringstream() << unitbuf
  • ostringstream() << noshowpos
  • Или любой другой стандартный манипулятор. [#include <iomanip>] Ссылка: См. «Вставить данные с форматом» 1/3 на этой веб-странице.

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

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

Использованная литература:

.

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

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

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

с кадабрами

ostringstream() << std::dec

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

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