16

Suppose we have

#include <chrono>
#include <iostream>
#include <ctime>

namespace Ratios { typedef std::ratio<60*60*24,1> Days; }

typedef std::chrono::system_clock Clock;
typedef Clock::time_point TimePoint;

And our main looks like

int main(int argc, char *argv[])
{
    // argc check left out for brevity
    const Clock::rep d = static_cast<Clock::rep>(std::atoi(argv[1]));
    // Right now
    TimePoint now = Clock::now();
    // Start with zero days
    auto days = std::chrono::duration<Clock::rep, Ratios::Days>::zero();

    // Now we'd like to add d to the days
    days += d; // Error!
    days.count() = d; // Error!
    days = days + d; // Error!
    days += std::chrono::duration<Clock::rep, Ratios::Days>(d); // Okay
    days = days + std::chrono::duration<Clock::rep, Ratios::Days>(d); // Okay

    days *= d; // Why is this okay?
    days %= d; // And this too?

    TimePoint later = now + days;

    return 0;
}

What is the reason behind prohibiting the user to manipulate a duration directly?

rwols
  • 2,968
  • 2
  • 19
  • 26

4 Answers4

24

It is done to enforce you to stick to strongly typed values rather than arbitrary values.

Bjarne Stroustrup has examples regarding this behaviour in "The C++ Programming Language" (4th Ed., 35.2.1, pp. 1011):


"The period is a unit system, so there is no = or += taking a plain value. Allowing that would be like allowing the addition of 5 of an unknown SI unit to a length in meters. Consider:

duration<long long, milli> d1{7}; // 7 milliseconds
d1 += 5; // error
[...]

What would 5 mean here? 5 seconds? 5 milliseconds? [...] If you know what you mean, be explicit about it. For example:

d1 += duration<long long, milli>{5}; //OK: milliseconds"

Pixelchemist
  • 24,090
  • 7
  • 47
  • 71
13

The rationale is to maintain the integrity of the unit of time which duration represents.

You can think of the rep as being unit-less. But the duration is has a unit of time. One can add and subtract seconds to/from seconds. But one can not add seconds and a unit-less quantity without making the expression ambiguous, and violating the algebra of units.

That being said, one can multiply and divide a unit of time by a scalar (a unit-less) quantity, and the result is still a unit of time. This library only represents units of time to the first power, or zero power. A unit of time raised to the zero power is a scalar and is represented by rep. Units of time can also have power of 2 or more, and negative powers. However this library does not represent such units.

When adding two quantities, the units must be the same.

When multiplying or dividing two quantities, a new unit is formed (e.g. km/hr). When quantities of the same units are multiplied, their exponents are added (e.g. sec * sec == sec^2). When quantities of the same units are divided, their exponents are subtracted (e.g. sec / sec == sec^0 == a scalar).

The std::chrono::duration library is a consistent subset of a physical quantities library that handles only units of time and only those units of time with exponents equal to 0 and 1.

Howard Hinnant
  • 206,506
  • 52
  • 449
  • 577
  • Of course the beautiful treatment of units breaks down when it comes to handling zero, since zero is independent of units. Of course this limitation comes from zero literals not having their own type in C++ -- it is difficult to accept zero without accepting arbitrary scalars. (It sure would be nice to be able to say `if (d < 0)` where `d` is some duration though.) – Ben Voigt Sep 25 '13 at 19:46
8
days += d; // Error!

This is because the variable days is in units of 86,400 seconds and the variable d is unitless. The result of adding a quantity of one unit to a unitless scalar is not defined under standard dimensional analysis.

days *= d; // Why is this okay?
days %= d; // And this too?

Because multiplying and dividing quantities by unitless scalars is not meaningless. Multiplying 2 seconds by 2 results in 4 seconds.

Consider multiplying 2 seconds by 3 seconds; the result is a quantity 6 with the unit 'seconds squared'. Of course chrono::duration isn't a complete units library so you can't have units like time squared, but libraries like boost.units will support that.

bames53
  • 86,085
  • 15
  • 179
  • 244
1

I would assume that this is done to force you to consider what the units of the duration you want to add/subtract are. It also keeps you from making any assumptions about what units the clock ticks are in.

Matt Kline
  • 10,149
  • 7
  • 50
  • 87