Stepan
Stepan

Reputation: 97

Overload operator<<

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?

Upvotes: 2

Views: 202

Answers (2)

Stamen Rakov
Stamen Rakov

Reputation: 474

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

Upvotes: 0

Benjamin Lindley
Benjamin Lindley

Reputation: 103761

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

Upvotes: 3

Related Questions