Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++ conversion operator to chrono::duration - works with c++17 but not C++14 or less

The following code compiles with gcc 7.1.0 with C++17 set but does not compile with C++14 set (or Visual Studio 2017). It is easy to reproduce on Wandbox.

What has to be done to make it work with C++11/14?

#include <iostream>
#include <chrono>

int main()
{
    struct Convert
    {
        operator std::chrono::milliseconds()
        {
            std::cout << "operator std::chrono::milliseconds" << std::endl;
            return std::chrono::milliseconds(10);
        }

        operator int64_t ()
        {
            std::cout << "operator int64_t" << std::endl;
            return 5;
        }
    };

    Convert convert;

    std::chrono::milliseconds m(convert);
    std::cout << m.count() << std::endl;
    int64_t i(convert);
    std::cout << i << std::endl;
}
like image 794
Slav Slavov Avatar asked Jan 21 '18 10:01

Slav Slavov


People also ask

What is std :: Chrono :: duration?

Class template std::chrono::duration represents a time interval. It consists of a count of ticks of type Rep and a tick period, where the tick period is a compile-time rational fraction representing the time in seconds from one tick to the next. The only data stored in a duration is a tick count of type Rep .

What is C++ conversion operator?

Conversion Operators in C++ C++ supports object oriented design. So we can create classes of some real world objects as concrete types. Sometimes we need to convert some concrete type objects to some other type objects or some primitive datatypes. To make this conversion we can use conversion operator.

What is Chrono C++?

Chrono in C++ chrono is the name of a header and also of a sub-namespace: All the elements in this header (except for the common_type specializations) are not defined directly under the std namespace (like most of the standard library) but under the std::chrono namespace.


1 Answers

Let's start with why this doesn't work in C++14. There are two relevant c'tors for std::chrono::duration (which std::chrono::milliseconds is aliased to be):

duration( const duration& ) = default;

template< class Rep2 >
constexpr explicit duration( const Rep2& r );

The templated one is a much better match for an argument of type Convert. But it will only participate in overload resolution if Rep2 (a.k.a Convert) is implicitly convertible to the representation type of std::chrono::duration. For milliseconds, that is long on Wandbox. Your int64_t conversion operator makes that implicit conversion possible.

But here's the catch. The check for this implicit conversion doesn't take the cv-qualifiers of the conversion member function into account. So that overload is chosen, but it accepts by a const reference. And your user defined conversion operator is not const qualified! @Galik noted it in the comments to your post. As such, the conversion fails inside the c'tor of milliseconds.

So how to resolve it? Two ways:

  1. Mark the conversion operator const. That will however pick the conversion to int64_t for both m and i.

  2. Mark the conversion to int64_t as explicit. Now the templated overload won't participate in overload resolution for m.

And finally, why does this work in C++17? That would be guaranteed copy elision. Since your Convert has a conversion to std::chrono::milliseconds, it is used to initialized m directly. The minutia of it involves not even needing to choose the copy constructor, just to elide it later.

like image 151
StoryTeller - Unslander Monica Avatar answered Oct 03 '22 15:10

StoryTeller - Unslander Monica