问题
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:
Mark the conversion operator
const
. That will however pick the conversion toint64_t
for bothm
andi
.Mark the conversion to
int64_t
asexplicit
. Now the templated overload won't participate in overload resolution form
.
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