Question

I would like to print a bunch of integers on 2 fields with '0' as fill character. I can do it but it leads to code duplication. How should I change the code so that the code duplication can be factored out?

#include <ctime>
#include <sstream>
#include <iomanip>
#include <iostream>

using namespace std;

string timestamp() {

    time_t now = time(0);

    tm t = *localtime(&now);

    ostringstream ss;

    t.tm_mday = 9; // cheat a little to test it
    t.tm_hour = 8;

    ss << (t.tm_year+1900)
       << setw(2) << setfill('0') << (t.tm_mon+1) // Code duplication
       << setw(2) << setfill('0') <<  t.tm_mday
       << setw(2) << setfill('0') <<  t.tm_hour
       << setw(2) << setfill('0') <<  t.tm_min
       << setw(2) << setfill('0') <<  t.tm_sec;

    return ss.str();
}

int main() {

    cout << timestamp() << endl;

    return 0;
}

I have tried

std::ostream& operator<<(std::ostream& s, int i) {

    return s << std::setw(2) << std::setfill('0') << i;
}

but it did not work, the operator<< calls are ambigous.


EDIT I got 4 awesome answers and I picked the one that is perhaps the simplest and the most generic one (that is, doesn't assume that we are dealing with timestamps). For the actual problem, I will probably use std::put_time or strftime though.

Était-ce utile?

La solution 2

You need a proxy for your string stream like this:

struct stream{
    std::ostringstream ss;
    stream& operator<<(int i){
        ss << std::setw(2) << std::setfill('0') << i;
        return *this; // See Note below
    }
} ss; 

Then your formatting code will just be this:

ss << (t.tm_year+1900)
   << (t.tm_mon+1)
   << t.tm_mday
   << t.tm_hour
   << t.tm_min
   << t.tm_sec;

return ss.ss.str();

ps. Note the general format of my stream::operator<<() which does its work first, then returns something.

Autres conseils

In C++20 you'll be able to do this with std::format in a less verbose way:

    ss << std::format("{}{:02}{:02}{:02}{:02}{:02}",
                      t.tm_year + 1900, t.tm_mon + 1, t.tm_mday,
                      t.tm_hour, t.tm_min, t.tm_sec);

and it's even easier with the {fmt} library that supports tm formatting directly:

auto s = fmt::format("{:%Y%m%d%H%M%S}", t);

The "obvious" solution is to use a manipulator to install a custom std::num_put<char> facet which just formats ints as desired.

The above statement may be a bit cryptic although it entirely describes the solution. Below is the code to actually implement the logic. The first ingredient is a special std::num_put<char> facet which is just a class derived from std::num_put<char> and overriding one of its virtual functions. The used facet is a filtering facet which looks at a flag stored with the stream (using iword()) to determine whether it should change the behavior or not. Here is the code:

class num_put
    : public std::num_put<char>
{
    std::locale loc_;
    static int index() {
        static int rc(std::ios_base::xalloc());
        return rc;
    }
    friend std::ostream& twodigits(std::ostream&);
    friend std::ostream& notwodigits(std::ostream&);

public:
    num_put(std::locale loc): loc_(loc) {}
    iter_type do_put(iter_type to, std::ios_base& fmt,
                     char fill, long value) const {
        if (fmt.iword(index())) {
            fmt.width(2);
            return std::use_facet<std::num_put<char> >(this->loc_)
                .put(to, fmt, '0', value);
        }
        else {
            return std::use_facet<std::num_put<char> >(this->loc_)
                .put(to, fmt, fill, value);
        }
    }
};

The main part is the do_put() member function which decides how the value needs to be formatted: If the flag in fmt.iword(index()) is non-zero, it sets the width to 2 and calls the formatting function with a fill character of 0. The width is going to be reset anyway and the fill character doesn't get stored with the stream, i.e., there is no need for any clean-up.

Normally, the code would probably live in a separate translation unit and it wouldn't be declared in a header. The only functions really declared in a header would be twodigits() and notwodigits() which are made friends in this case to provide access to the index() member function. The index() member function just allocates an index usable with std::ios_base::iword() when called the time and it then just returns this index. The manipulators twodigits() and notwodigits() primarily set this index. If the num_put facet isn't installed for the stream twodigits() also installs the facet:

std::ostream& twodigits(std::ostream& out)
{
    if (!dynamic_cast<num_put const*>(
             &std::use_facet<std::num_put<char> >(out.getloc()))) {
        out.imbue(std::locale(out.getloc(), new num_put(out.getloc())));
    }
    out.iword(num_put::index()) = true;
    return out;
}

std::ostream& notwodigits(std::ostream& out)
{
    out.iword(num_put::index()) = false;
    return out;
}

The twodigits() manipulator allocates the num_put facet using new num_put(out.getloc()). It doesn't require any clean-up because installing a facet in a std::locale object does the necessary clean-up. The original std::locale of the stream is accessed using out.getloc(). It is changed by the facet. In theory the notwodigits could restore the original std::locale instead of using a flag. However, imbue() can be a relatively expensive operation and using a flag should be a lot cheaper. Of course, if there are lots of similar formatting flags, things may become different...

To demonstrate the use of the manipulators there is a simple test program below. It sets up the formatting flag twodigits twice to verify that facet is only created once (it would be a bit silly to create a chain of std::locales to pass through the formatting:

int main()
{
    std::cout << "some-int='" << 1 << "' "
              << twodigits << '\n'
              << "two-digits1='" << 1 << "' "
              << "two-digits2='" << 2 << "' "
              << "two-digits3='" << 3 << "' "
              << notwodigits << '\n'
              << "some-int='" << 1 << "' "
              << twodigits << '\n'
              << "two-digits4='" << 4 << "' "
              << '\n';
}

Besides formatting integers with std::setw / std::setfill or ios_base::width / basic_ios::fill, if you want to format a date/time object you may want to consider using std::put_time / std::gettime

For convenient output formatting you may use boost::format() with sprintf-like formatting options:

#include <boost/format.hpp>
#include <iostream>

int main() {
    int i1 = 1, i2 = 10, i3 = 100;
    std::cout << boost::format("%03i %03i %03i\n") % i1 % i2 % i3; 
    // output is: 001 010 100
}

Little code duplication, additional implementation effort is marginal.


If all you want to do is output formatting of your timestamp, you should obviously use strftime(). That's what it's made for:

#include <ctime>
#include <iostream>

std::string timestamp() {
    char buf[20];
    const char fmt[] = "%Y%m%d%H%M%S";
    time_t now = time(0);
    strftime(buf, sizeof(buf), fmt, localtime(&now));
    return buf;
}

int main() {
    std::cout << timestamp() << std::endl;
}

operator<<(std::ostream& s, int i) is "ambiguous" because such a function already exists.

All you need to do is give that function a signature that doesn't conflict.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top