Как реализовать пользовательский липкий манипулятор, который автоматически добавляет разделители?

Функция печати в 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, Кроме того, он не может просто работать с числами или определенными типами, такими как Вот. Он должен работать с любым типом стрима.

2

Решение

Проблема с решением, которое вы опубликовали, заключается в том, что оно основывается на настройке форматирования целых чисел с использованием фасетов. К сожалению, я не думаю, что есть соответствующее средство, которое бы работало для произвольных типов.

Есть. Вы можете использовать использовать базовый буфер потока, чтобы получить то, что вы хотите. В этом буфере последовательность символов в конечном итоге собирается для обслуживания. Следующий код создает буфер потока, который содержит ссылку на объект, последовательность символов которого вы хотите использовать. Мы устанавливаем 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;
}

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

Живой пример

3

Другие решения

Итак, это демонстративно не самое чистое / самое короткое решение, но вот один из способов сделать это:

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;

Где первое решение сверху не подойдет для этого.

2

По вопросам рекламы [email protected]