Multiplying integral chrono duration by float has unintuitive behavior

818 views
Skip to first unread message

ssha...@gmail.com

unread,
Jul 6, 2016, 10:45:27 AM7/6/16
to ISO C++ Standard - Discussion, Kent Huynh
The integral chrono duration types (the defaults) only define arithmetic operators for integral literals.  They do not provide such operators for floating point literals.

This can lead to very unintuitive behavior if one attempts to multiply by a floating point. Consider the following sample program:

#include <chrono>
#include <iostream>

int main(int, char**)
{
    using namespace std::chrono;
    milliseconds base(60 * 1000);
    milliseconds derived = base;
    derived *= 0.8;
    
    std::cout << "Base: " << base.count()
        << ", derived: " << derived.count()
        << std::endl;
}

This prints 'Base: 60000, Derived: 0', which I doubt is what anybody who writes this code intends.

Is there anything that can be done to help people avoid this trap? My first two thoughts were:
 - Declare the non-member floating point overloads as deleted to prevent the implicit conversion to int
 - Just add the floating point overloads


Howard Hinnant

unread,
Jul 6, 2016, 11:10:00 AM7/6/16
to std-dis...@isocpp.org, Kent Huynh
On Jul 6, 2016, at 10:45 AM, ssha...@gmail.com wrote:
>
> The integral chrono duration types (the defaults) only define arithmetic operators for integral literals. They do not provide such operators for floating point literals.
>
> This can lead to very unintuitive behavior if one attempts to multiply by a floating point. Consider the following sample program:
>
> #include <chrono>
> #include <iostream>
>
> int main(int, char**)
> {
> using namespace std::chrono;
> milliseconds base(60 * 1000);
> milliseconds derived = base;
> derived *= 0.8;
>
> std::cout << "Base: " << base.count()
> << ", derived: " << derived.count()
> << std::endl;
> }
>
> This prints 'Base: 60000, Derived: 0', which I doubt is what anybody who writes this code intends.

On the plus side, my complier outputs the following warning by default (not using -Wall):

test.cpp:10:16: warning: implicit conversion from 'double' to 'rep' (aka 'long long') changes value from 0.8 to 0 [-Wliteral-conversion]
derived *= 0.8;
~~ ^~~
1 warning generated.

Gcc does not warn by default, but will if you use -Wfloat-conversion.

I don’t see any warnings at all from VS.

>
> Is there anything that can be done to help people avoid this trap? My first two thoughts were:
> - Declare the non-member floating point overloads as deleted to prevent the implicit conversion to int
> - Just add the floating point overloads

I would not be adverse to restricting these operators using treat_as_floating_point. Though I would want the proposer to actually implement and test such a suggestion first. The rationale to using the trait is so that we correctly handle emulated arithmetic reps (e.g. safe<int>, safe<double>).

Here is how I would recommend fixing the client code:

#include <chrono>
#include <iostream>

int
main()
{
using namespace std::chrono_literals;
using milliseconds = std::chrono::duration<double, std::milli>;
milliseconds base = 1min;
milliseconds derived = base;
derived *= 0.8;

std::cout << "Base: " << base.count()
<< ", derived: " << derived.count()
<< std::endl;
}

Howard

signature.asc
Reply all
Reply to author
Forward
0 new messages