2

I would like to overload operator<< like this:

ostringstream oss;
MyDate a(2000, 1, 2);
oss << dateFormat("%Y/%m/%d") << a;
assert(oss.str() == "2000-01-02");

so that the date at a will be formatted to specific format. How to achieve this?

MSeifert
  • 145,886
  • 38
  • 333
  • 352
Stepan
  • 97
  • 2
  • 11

2 Answers2

3

In order to store custom state in a stream, you need to use the xalloc static function to get a unique index, and then either pword to get a pointer at that index (allocated specifically for each stream it is used on), or iword to get an integer at that index(allocated specifically for each stream it is used on). In your case, you will probably want pword. You can use the pointer returned by pword to point to a dynamically allocated object which stores the formatting information.

struct DateFormatter
{
    // The implementation of this class (e.g. parsing the format string)
    // is a seperate issue. If you need help with it, you can ask another
    // question

    static int xalloc_index;
};

int DateFormatter::xalloc_index = std::ios_base::xalloc();

void destroy_date_formatter(std::ios_base::event evt, std::ios_base& io, int idx)
{
    if (evt == std::ios_base::erase_event) {
        void*& vp = io.pword(DateFormatter::xalloc_index);
        delete (DateFormatter*)(vp);
    }
}

DateFormatter& get_date_formatter(std::ios_base& io) {
    void*& vp = io.pword(DateFormatter::xalloc_index);
    if (!vp) {
        vp = new DateFormatter;
        io.register_callback(destroy_date_formatter, 0);
    }
    return *static_cast<DateFormatter*>(vp);
}

std::ostream& operator<<(std::ostream& os, const DateFormatter& df) {
    get_date_formatter(os) = df;
    return os;
}

std::ostream& operator<<(std::ostream& os, const MyDate& date)
{
    DateFormatter& df = get_date_formatter(os);

    // format output according to df

    return os;
}

int main() {
    MyDate a ( 2000, 1, 2 );
    std::cout << DateFormatter("%Y/%m/%d") << a;
}

This is the standard method. It is terrible, in my opinion. I much prefer an alternative approach, which is to pass the date object together with the formatting as a single object. For example:

class DateFormatter
{
    const MyDate* date;
    std::string format_string;
    DateFormatter(const MyDate& _date, std::string _format_string)
        :date(&_date)
        ,format_string(_format_string)
    {}

    friend std::ostream& operator<<(std::ostream& os, const DateFormatter& df) {
        // handle formatting details here
        return os;
    }
};

int main() {
    MyDate a ( 2000, 1, 2 );
    std::cout << DateFormatter(a, "%Y/%m/%d");
}
Benjamin Lindley
  • 101,917
  • 9
  • 204
  • 274
  • How/when does the DateFormatter get deleted in the first example? – kfsone Apr 17 '16 at 05:56
  • @ksfone: If you mean when is the delete statement used on it, never. Its lifetime ends with the program. – Benjamin Lindley Apr 17 '16 at 06:05
  • @kfsone: You know what? This is bad. I was thinking that only one of these objects would be created, but I just realized that one will be created for every stream that is created (which could be a problem with something like stringstreams, which might get created over and over). I don't do this often, so I forgot the proper way to do it. I'll fix it. – Benjamin Lindley Apr 17 '16 at 06:10
0

Or you can do something like that (using static variable):

#include <iostream>

struct MyDate
{
    MyDate(int y, int m, int d): year{y}, month{m}, day{d} {}

    int year{};
    int month{};
    int day{};
};

class DateFormatter
{
public:
    DateFormatter(const std::string & format)
    {
        format_ = format;
    }

    static const std::string & format()
    {
        return format_;
    }

private:
    static std::string format_;
};

std::string DateFormatter::format_ = {"Default Format"};

std::ostream & operator<< (std::ostream & stream, const DateFormatter &)
{
    return stream;
}

std::ostream & operator<< (std::ostream & stream, const MyDate & date)
{
    auto currentFormat = DateFormatter::format();
    // some code using current format ...
    return stream << currentFormat << " - " << date.year << "/" << date.month << "/" << date.day;
}

int main(void)
{
    MyDate date{2016,4,18};
    std::cout << date << std::endl;
    std::cout << DateFormatter("New format") << date << std::endl;

    return 0;
}
Stamen Rakov
  • 456
  • 4
  • 10