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...