Функция печати в Python автоматически разделяет свои аргументы настраиваемым разделителем. Есть ли способ эмулировать это поведение в C ++ с помощью потоковых манипуляторов?
То есть следующий код C ++:
std::cout << custom::sep(", ") << 1 << "two" << 3 << std::endl;
Должен работать аналогично следующему коду Python:
print(1, "two", 3, sep=", ")
Желаемый результат будет:
1, two, 3
Как бы я пошел о реализации custom::sep
? Это кажется немного сложнее, чем ваш стандартный пользовательский манипулятор, потому что он не может просто изменить следующий элемент в потоке, как Вот или же Вот. Это должно быть липким до следующего custom::sep
или же std::endl
, Кроме того, он не может просто работать с числами или определенными типами, такими как Вот. Он должен работать с любым типом стрима.
Проблема с решением, которое вы опубликовали, заключается в том, что оно основывается на настройке форматирования целых чисел с использованием фасетов. К сожалению, я не думаю, что есть соответствующее средство, которое бы работало для произвольных типов.
Есть. Вы можете использовать использовать базовый буфер потока, чтобы получить то, что вы хотите. В этом буфере последовательность символов в конечном итоге собирается для обслуживания. Следующий код создает буфер потока, который содержит ссылку на объект, последовательность символов которого вы хотите использовать. Мы устанавливаем std::ios_base::unitbuf
флаг формата, чтобы поток сбрасывался при каждой операции вывода (чтобы мы могли добавить разделитель в конец).
Кроме того, он также позволяет вам удалить разделитель и убедиться, что в процессе нет утечки памяти:
#include <iostream>
namespace custom
{
struct sep_impl
{
sep_impl(std::string const& separator);
std::string separator;
};
sep_impl sep(std::string const& str)
{
return sep_impl(str);
}
std::ostream& nosep(std::ostream& os);
}
int separatorEnabled()
{ static int idx = std::ios_base::xalloc(); return idx; }
int getSeparator() { static int idx = std::ios_base::xalloc(); return idx; }
struct custom_separator : std::streambuf
{
public:
custom_separator(std::ostream& _stream) : stream(_stream)
{ }
int_type overflow(int_type c)
{
return stream.rdbuf()->sputc(c);
}
int sync()
{
if (stream.iword(separatorEnabled()))
{
void*& p = stream.pword(getSeparator());
stream << *static_cast<std::string*>(p);
return 0;
}
return stream.rdbuf()->pubsync();
}
private:
std::ostream& stream;
};
void cleanup(std::ios_base::event evt, std::ios_base& str, int idx)
{
if (str.iword(separatorEnabled()) && evt == std::ios_base::erase_event)
{
void*& p = str.pword(idx);
delete static_cast<std::string*>(p);
str.iword(separatorEnabled()) = false;
}
}
std::ostream& set_separator(std::ostream& os, const custom::sep_impl& manip)
{
if (!os.bad())
{
os.pword(getSeparator()) = new std::string(manip.separator);
os.register_callback(cleanup, getSeparator());
}
return os;
}
std::ostream& operator<<(std::ostream& os, const custom::sep_impl& manip)
{
std::ostream* p = os.tie();
if (p && !p->iword(separatorEnabled())
{
set_separator(*p, manip);
p->iword(separatorEnabled()) = true;
}
return os << std::unitbuf;
}
namespace custom
{
sep_impl::sep_impl(std::string const& _sep) : separator(_sep) { }
std::ostream& nosep(std::ostream& os)
{
cleanup(std::ios_base::erase_event, *os.tie(), getSeparator());
os.tie(nullptr);
return os << std::nounitbuf;
}
void install_separator(std::ostream& o1, std::ostream& o2)
{
static custom_separator csep(o2);
o1.rdbuf(&csep);
o1.tie(&o2);
}
}
int main()
{
std::ostream os(nullptr);
custom::install_separator(os, std::cout);
os << custom::sep(", ") << 4 << 2 << custom::nosep;
}
Я уверен, что есть также возможности для улучшения, поэтому, если у кого-то есть какие-либо предложения, они очень ценятся.
Итак, это демонстративно не самое чистое / самое короткое решение, но вот один из способов сделать это:
namespace custom
{
struct sep
{
sep(const std::string & s)
:separator(s)
{
}
std::string separator;
};
}
typedef std::basic_ostream<char, std::char_traits<char> > CoutType;
typedef CoutType& (*StandardEndLine)(CoutType&);
class SeparatorWrap
{
public:
SeparatorWrap(std::ostream & _ofs, const custom::sep & s)
: ofs(_ofs)
, separator(s)
{}
template <class W>
SeparatorWrap& operator << (W && w)
{
ofs << separator.separator << w;
return (*this);
}
ostream & operator << (const StandardEndLine &)
{
//writing std::endl will remove the separator
return ofs << std::endl;
}
protected:
std::ostream & ofs;
custom::sep separator;
};
class SeparatorWrapFirst
{
public:
SeparatorWrapFirst(std::ostream & _ofs, const custom::sep & s)
: ofs(_ofs)
, separator(s)
{}
template <class W>
SeparatorWrap operator << (W && w)
{
ofs << w;
return SeparatorWrap(ofs, separator);
}
ostream & operator << (const StandardEndLine &)
{
//writing std::endl will remove the separator
return ofs << std::endl;
}
protected:
std::ostream & ofs;
custom::sep separator;
};
SeparatorWrapFirst operator << (std::ostream & ofs,const custom::sep & s)
{
return SeparatorWrapFirst(ofs, s);
}int main()
{
std::cout << custom::sep(", ") << 1 << "two" << 3 << std::endl;
}
Вот как это работает:
std::cout << custom::sep(", ")
возвращает класс типа SeparatorWrapFirst
(используя глобальный operator <<
), который используется для записи одного значения без разделителей вывода. Это потому, что если у вас есть один элемент, вам не нужно писать разделитель.
После первого оператора <<
от SeparatorWrapFirst
называется, класс SeparatorWrap
возвращается и печатается с разделителем также. Это для нескольких значений.
Редактировать:
Так что из комментариев (@gexicide) видно, что я могу поставить пользовательский манипулятор внутри std::cout
. Это может позволить вам сделать что-то вроде:
std::cout << custom::sep(", ");
std::cout << 1 << "two" << 3 << std::endl;
Где первое решение сверху не подойдет для этого.