3

I do not understand how you are supposed to scale a std::chrono::duration. What is the correct way to scale a duration by a floating point number?

template <class Duration>
auto random_duration(Duration low, Duration high) noexcept -> Duration {
    static auto rd = std::random_device();
    static auto re = std::default_random_engine(rd());
    auto dist = std::uniform_real_distribution<double>(0, 1);
    const auto off = std::chrono::duration<Duration>(dist(re) * (high - low));
    return low + off;
}
Holt
  • 36,600
  • 7
  • 92
  • 139
Paul
  • 360
  • 3
  • 10
  • 2
    what is wrong with the code you have? You do scale the duration by a random factor, no? – 463035818_is_not_an_ai Jul 24 '19 at 08:23
  • 1
    @formerlyknownas_463035818 It doesn't compile. – lubgr Jul 24 '19 at 08:26
  • 1
    A correct way of scaling a duration by a floating-point number is to "multiply" them by `std::chrono::duration::operator*` (see, e.g., https://wandbox.org/permlink/kWdtlyShNEjGAwSC). What is your problem? Please, provide a reproducible example. What is your error? What do you pass as a template argument? – Daniel Langr Jul 24 '19 at 08:26
  • 3
    please include the compiler error in the question and provide a [mcve] – 463035818_is_not_an_ai Jul 24 '19 at 08:27
  • 1
    @lubgr It compiles for me: https://wandbox.org/permlink/xRvGJuSl5pAEkMxn. – Daniel Langr Jul 24 '19 at 08:29
  • 1
    Just remove the cast to `std::chrono::duration` and you'll get what you want. – Holt Jul 24 '19 at 08:33
  • 1
    @Holt Doesn't that depend on the `Duration` type you pass in, i.e., it might work but could also fail? – lubgr Jul 24 '19 at 08:34
  • 2
    @lubgr Sorry I did not see that OP wanted to return a `Duration`, which is very weird... If remove the `std::chrono::duration` and the explicit return type, you'll get an automatic return type, which is probably what you want. Casting to `Duration` will lose a lot of precision, which is probably not what you want. – Holt Jul 24 '19 at 08:37
  • 1
    @Paul You want to scale by a floating point number and then "cast" to the original representation? E.g., `1.3 * (4s - 2s)` -> `2.6s` -> `2s`? – Holt Jul 24 '19 at 08:40
  • 1
    Possibly relevant question: [How to get duration, as int milli's and float seconds from ?](https://stackoverflow.com/q/14391327/580083). – Daniel Langr Jul 24 '19 at 08:42
  • 2
    Style guide 1: This function is overly generic in its template parameter. It accepts anything. Templatize on `Rep` and `Period` and make the parameter `duration`. – Howard Hinnant Jul 24 '19 at 13:23
  • 2
    Style guide 2: This function needlessly restricts the two duration parameters to have the same units. Let them be different: `template auto random_duration(duration, duration)`. – Howard Hinnant Jul 24 '19 at 13:25
  • 2
    Style guide 3: Let the function return the type that is the `common_type` of the two durations multiplied by a `double`. This is most easily accomplished by deducing the return type (requires C++14). This also implies loosing all casts within the implementation of the function. The _client_ of this function can decide if he wants to change the type of the result to something else. `template auto random_duration(duration, duration) {...}`. – Howard Hinnant Jul 24 '19 at 13:28

1 Answers1

3

First of all, if you instantiate this template with two std::duration<double, ...> objects, everything works fine and as expected. The issue you encouter is probably that the instance you get by operator* might not implicitly convertible to the trailing return type (Duration) when the input type is something like std::duration<int, ...>. In this case, scaling it by a double is a lossy operation, which (if you intend this) is fine on its own, but return it is as a Duration is not. <chrono> doesn't permit such a conversion unless you are explicit. This is a good thing.

Here is how it could work.

const auto off = dist(re)*((high - low));

return low + std::chrono::duration_cast<Duration>(off);

So you need to explicitly do the possibly lossy conversion of the input durations to one that can be multiplied by a double. Once you have that, in order to be compliant with the trailing return type Duration, you need to explicitly convert it back.

lubgr
  • 37,368
  • 3
  • 66
  • 117
  • 1
    `std::chrono::seconds d1(10); auto d2 = 2.5 * d1;` This compiles for me despite that the `Rep` type for `seconds` is integral. – Daniel Langr Jul 24 '19 at 08:32
  • 2
    Cppreference says for `operator*`: _Converts the duration `d` to one whose `rep` is the **common type** between `Rep1` and `Rep2`, and multiples the number of ticks after conversion by `s`._ – Daniel Langr Jul 24 '19 at 08:35
  • 3
    Ok, I get it know. The `auto d2` you get (wandbox link above) is a `std::duration` because that's the result with the `common_type` template that determines the return type of `operator`. The OP's issue is that the return type as the _original_ representation, and that is a lossy conversion. – lubgr Jul 24 '19 at 08:37