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