C++ streams operator << and manipulators / formatters
First, most of my recent work was Java. So even though I "know" C++, I do not want to write Java in C++.
And C++ templates are one thing I will really miss when going back to Java.
Now that this out of the way, if I want to do create a new stream formatter, say pic, that will have a single std::string parameter in it开发者_C百科's constructor.
I would like the user to be able to write something like:
cout << pic("Date is 20../../..") << "100317" << endl;
The output should be
Date is 2010/03/17
How do I write the pic class? when the compiler sees the cout what are the underlying steps the compiler does?
Edit Would it be more C++ to change that code into:
cout << pic("Date is 20../../..", "100317") << endl;
And possibly be easier to write the pic function as a standalone function (possibly template)?
It sounds like you are trying to write an alternate form of printf(). I'm not sure that this is a good idea, but if you decide to do it, you should definitely write it as a free function, because the manipulator issues (which have nothing to do with formatting using a format string) go away. I would also avoid templates to start with, and simply design and write the string version:
void pic( ostream & os, const string & fmt, const string & val );
Before writing such a function, you will have to be very clear in your mind what its semantics are, which I don't believe you are yet.
you might have a look at boost::format library.
somethings like this should work (if you can afford spliting your string first)
#include <iostream>
#include <string>
#include <boost/format.hpp>
int main()
{
const char* a = "102030";
std::string year(a, a + 2);
std::string month(a + 2, a +4);
std::string day(a + 4);
std::cout << boost::format("Date is 20%1%/%2%/%3%")% year % month % day << std::endl;
}
There are 2 questions here.
One deals with stream manipulators, follow the quote.
The other one deals with formatting issues.
Formatting is hard, especially the way you indicate it, because it involves being able to parse the format and generate an AST representation that will then be called to actually format the string. Parsing means that you need to define a small grammar etc...
There are libraries like Boost.Spirit who deals with parsing / generating, and they are much more complicated than the 'simple' Boost.Format (which itself is not that simple).
Now, could you forgo parsing ?
class Date
{
public:
Date(year_t year, month_t month, day_t day);
year_t getYear() const;
month_t getMonth() const;
day_t getDay() const;
private:
year_t mYear;
month_t mMonth;
day_t mDay;
};
The advantage of this class are multiple:
- Structured information: parses one, reads as much as you wish
- Validation: root out invalid date (29 Feb 2010 ?)
- No ambiguity: is "100102" actually the "1st Feb 2010" or "2nd Jan 2010" ? (at least not once it's parsed)
Then, you can do the same for the format, by creating a little formatting engine.
template <class T>
class Formatter
{
public:
virtual ~Formatter() {}
virtual Formatter* clone() const = 0;
virtual std::string evaluate(const T& item) const = 0;
};
template <class T>
class FormatterConstant: public Formatter
{
public:
explicit FormatterConstant(const std::string& s): mValue(s) {}
virtual Formatter<T>* clone() const { return new FormatterConstant(*this); }
virtual std::string evaluate(const T&) const { return mValue; }
private:
std::string mValue;
};
template <class T>
class FormatterComposite: public Formatter<T>
{
typedef std::vector< const Formatter<T>* > formatters_type;
typedef typename formatters_type::const_iterator const_iterator;
public:
// Need suitable Copy and Assignment Constructors
~FormatterComposite()
{
for(const_iterator it = mFormatters.begin(), end = mFormatters.end();
it != end; ++it) delete *it;
}
virtual Formatter<T>* clone() const { return new FormatterComposite(*this); }
virtual std::string evaluate(const T& item) const
{
std::string result;
for(const_iterator it = mFormatters.begin(), end = mFormatters.end();
it != end; ++it) result += (*it)->evaluate();
return result;
}
void imbue(std::ostream& s) { mStream = &s; }
FormatterComposite& operator<<(const std::string& s)
{
mFormatters.push_back(new FormatterConstant<T>(s); }
return *this;
}
FormatterComposite& operator<<(const Formatter<T>& formatter)
{
mFormatters.push_back(formatter.clone());
return *this;
}
std::ostream& operator<<(const T& item) const
{
return (*mStream) << this->evaluate(item);
}
private:
std::ostream* mStream;
formatters_type mFormatters;
};
template <class T>
FormatterComposite& operator<<(std::ostream& s, FormatterComposite& c)
{
c.imbue(s);
return c;
}
// Usage
class DateOfYear: public Formatter<Date>
{
public:
Formatter<Date>* clone() const { return new DateOfYear(*this); }
std::string evaluate(const Date& d) const { return toString(d.getYear()); }
};
extern const DateOfYear year;
int main(int argc, char* argv[])
{
FormatterComposite<Date> formatter;
Date date;
std::cout << formatter << "Date is 20"
<< year << "/" << month << "/" << day << date;
return 0;
}
Here, you forgo parsing. Of course it means the format is hardcoded...
Here is the initial solution to what I did. Only issue is that I'm still not able to templatize it. If I do, then calling the pic formatter would look like pic<float>("$(...)", 2.56)
and the code would be messy.
#include <iostream>
#include <string>
using namespace std;
class pic {
private:
const string& _v;
const string& _pic;
public:
pic(const string& p, const string& v) : _v(v), _pic(p) {
}
friend ostream & operator<<(ostream& os, const pic& p) {
bool done = false;
int pi = 0;
int vi = 0;
while (!done) {
os << (p._pic[pi] == '.' ? p._v[vi++] : p._pic[pi]);
done = ++pi > p._pic.length() || vi > p._v.length();
}
return os;
}
};
int main(int argc, char** argv) {
cout << "The formatted date is: " << pic("20../../..", "100317") << endl;
return 0;
}
精彩评论