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

微笑、不失礼 提交于 2020-01-03 08:35:30

问题


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;
}

回答1:


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.




回答2:


This is a pretty interesting case. The reason it fails to compile on C++14 is correctly explained by StoryTeller, and is arguably an LWG defect - the requirement on the converting constructor is that Rep2 is convertible to rep, but the body of that constructor attempts to convert a Rep2 const to rep - and in the particular example in OP, this is ill-formed. This is now LWG 3050.

In C++17 though, none of the relevant, articulated rules in the standard have changed. Direct-initialization (e.g. std::chrono::milliseconds m(convert);) still just considers constructors, and the best match from overload resolution amongst the constructors would still be the very same defective converting constructor that causes the program to fail to compilein C++14.

However, there is an outstanding core issue that both gcc and clang have apparently decided to implement, despite there not being wording for it yet. Consider:

struct A 
{ 
  A(); 
  A(const A&) = delete; 
}; 
struct B 
{ 
  operator A(); 
}; 

B b; 
A a1 = b; // OK 
A a2(b);  // ? 

Per the language rules today, copy-initializing from b is okay, we use the conversion function. But direct-initializing from b is not okay, we would have to use A's deleted copy constructor.

Another motivating example is:

struct Cat {};
struct Dog { operator Cat(); };

Dog d;
Cat c(d);

Here, again, we'd have to go through Cat(Cat&& ) - which in this case is well-formed, but inhibits copy elision due to temporary materialization.

So the suggested resolution of this issue is to consider both constructors and conversion functions for direct-initialization. In both examples here and in the OP's example, this would yield the "expected" behavior - direct-initialization would just use the conversion function as being the better match. Both gcc and clang take this route in C++17 mode, which is why the examples compiles today.



来源:https://stackoverflow.com/questions/48365690/c-conversion-operator-to-chronoduration-works-with-c17-but-not-c14-or

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!