Вопрос оператора потока С++
Я предполагаю, что это может быть простым вопросом для всех гуру здесь, но я как-то не мог понять ответ.
Я хочу, чтобы писать ячейки csv в поток так же просто, как это:
stream << 1 << 2 << "Tom" << std::endl;
который создавал бы выход, например, 1,2, Том. Как я могу это достичь? Я решил, что мне нужно создать пользовательский streambuf (поскольку я не думаю, что это правильный способ сделать это на уровне потока, было бы настоящей болью просто перегрузить < < для всех типов), но я не уверен как < обычно реализуется. Позволяет ли он называть или писать или что. Должен ли я переопределить те или что? Или я просто что-то пропустил?
Буду признателен за любую помощь:)
Приветствия,
Ответы
Ответ 1
Получение чего-то вроде 98% от того, как это не так сложно:
#include <iostream>
class add_comma {
std::ostream &os;
bool begin;
typedef add_comma &ref;
public:
add_comma(std::ostream &o) : os(o), begin(true) {}
template <class T>
ref operator<<(T const &t) {
if (!begin)
os << ",";
os << "\"" << t << "\"";
begin = false;
return *this;
}
ref operator<<(std::ostream &manip(std::ostream &o) ) {
if (&manip == &std::endl)
reset();
manip(os);
return *this;
}
void reset() { begin = true; }
operator void *() { return (void *)os; }
};
int main() {
add_comma a(std::cout);
a << 1 << 2 << "This is a string" << std::endl;
a << 3 << 4 << "Another string" << std::endl;
return 0;
}
Изменить: я исправил код как минимум в некоторой степени - теперь он помещает только запятые между элементами, которые написаны, а не в начале строки. Однако он признает "endl" как сигнализацию начала новой записи - например, новая строка в строковом литерале не будет работать.
Ответ 2
В то время как я могу оценить идею перегрузки оператора потока, я бы поставил под сомнение практику для этой проблемы.
1. Объектно-ориентированный подход
Если вы хотите написать файл .csv
, то каждая строка должна, вероятно, иметь тот же формат, что и другие? К сожалению, оператор вашего потока не проверяет его.
Я думаю, что вам нужно создать объект Line
, чем будет потокобезопасным, и будет проверять каждое поле перед записью их в файл (и записать их в соответствующем формате). Хотя это не так модно, у вас будет гораздо больше шансов на достижение надежной реализации здесь.
Скажем, что (например) вы хотите вывести 2 целых числа и строку:
class Line
{
public:
Line(int foo, int bar, std::string firstName):
mFoo(foo), mBar(bar), mFirstName(firstName)
friend std::ostream& operator<<(std::ostream& out, const Line& line)
{
return out << line.mFoo << ',' << line.mBar << ','
<< line.mFirstName << std::endl;
}
private:
int mFoo;
int mBar;
std::string mFirstName;
};
И использование его остается очень простым:
std::cout << Line(1,3,"Tom") << Line(2,4,"John") << Line(3,5,"Edward");
2. Хочешь повеселиться?
Теперь это может показаться скучным, и вы могли бы захотеть сыграть и все еще иметь некоторый контроль над тем, что написано... ну, позвольте мне представить мета-программирование шаблонов в fray;)
Вот предполагаемое использование:
// Yeah, I could wrap this mpl_::vector bit... but it takes some work!
typedef CsvWriter< mpl_::vector<int,int,std::string> > csv_type;
csv_type(std::cout) << 1 << 3 << "Tom" << 2 << 4 << "John" << 3 << 5 << "Edward";
csv_type(std::cout) << 1 << 2 << 3; // Compile Time Error:
// 3 is not convertible to std::string
Теперь это было бы интересно? Он отформатировал бы линию и обеспечил бы меру проверки... Всегда можно было усложнить дизайн, чтобы он делал больше (например, регистрировать валидаторы для каждого поля или для всей строки и т.д.), Но он уже достаточно сложный.
// namespace mpl_ = boost::mpl
/// Sequence: MPL sequence
/// pos: mpl_::size_t<N>, position in the Sequence
namespace result_of {
template <class Sequence, class pos> struct operator_in;
}
template < class Sequence, class pos = mpl_::size_t<0> >
class CsvWriter
{
public:
typedef typename mpl_::at<Sequence,pos>::type current_type;
typedef typename boost::call_traits<current_type>::param_type param_type;
CsvWriter(std::ostream& out): mOut(out) {}
typename result_of::operator_in<Sequence,pos>::type
operator<<(param_type item)
{
typedef typename result_of::operator_in<Sequence,pos>::type result_type;
if (pos::value != 0) mOut << ',';
mOut << item;
if (result_type::is_last_type::value) mOut << std::endl;
return result_type(mOut);
}
private:
std::ostream& mOut;
}; // class CsvWriter
/// Lil' bit of black magic
namespace result_of { // thanks Boost for the tip ;)
template <class Sequence, class pos>
struct operator_in
{
typedef typename boost::same_type<
typename mpl_::size<Sequence>::type,
typename mpl_::next<pos>::type
> is_last_type;
typedef typename mpl_::if_<
is_last_type,
CsvWriter< Sequence, mpl_::size_t<0> >,
CsvWriter< Sequence, typename mpl_::next<pos>::type >
>::type;
}; // struct operator_in<Sequence,pos>
} // namespace result_of
Здесь у вас есть писатель потока, который гарантирует, что файл cvs будет правильно отформатирован... закрашивание символов новой строки в строках;)
Ответ 3
Если A - итератор над элементами...
copy(A, A + N, ostream_iterator<int>(cout, ","));