I suppose this might be simple question for all the gurus here but I somehow couldn't figure out the answer.
I want to be able to write csv cells to stream as simple as this:
stream << 1 << 2 << "Tom" << std::endl;
which would create output like 1,2,Tom. How can I achieve that? I figured that I need to create custom streambuf (as I don't think it's the right way to do it on stream level, it would be real pain just to overload << for all the types) but I'm not sure how << is normally implemented. Does it call put or write or what. Should I override those or what? Or did I just miss something completely?
I'd appreciate any help :)
Cheers,
Getting something like 98% of the way there isn't terribly difficult:
#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;
}
Edit: I've fixed the code to at least some degree -- it now only puts commas between items that are written, not at the beginning of a line. It only, however, recognizes "endl" as signaling the beginning of a new record -- a newline in a string literal, for example, won't work.
While I can appreciate the idea of overloading the stream operator, I would question the practice for the problem at hand.
1. Object-Oriented approach
If you are willing to write in a .csv
file, then each line should probably have the very same format than the others ? Unfortunately your stream operator does not check it.
I think that you need to create a Line
object, than will be streamable, and will validate each field before writing them to the file (and write them with the proper format). While not as fashionable, you'll have much more chance of achieving a robust implementation here.
Let's say that (for example) you want to output 2 integers and a string:
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;
};
And using it remains very simple:
std::cout << Line(1,3,"Tom") << Line(2,4,"John") << Line(3,5,"Edward");
2. Wanna have fun ?
Now, this may seem dull, and you could wish to play and yet still have some control over what is written... well, let me introduce template meta programming into the fray ;)
Here is the intended usage:
// 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
Now that would be interesting right ? It would format the line and ensure a measure of validation... One could always complicate the design so that it does more (like registering validators for each field, or for the whole line, etc...) but it's already complicated enough.
// 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
Here you have a stream writer that ensures that the cvs file is properly formatted... baring newlines characters in the strings ;)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With