Pardon the convoluted title, and consider this code:
#include <memory>
#include <utility>
using X = std::unique_ptr<int>;
using A = std::pair<int, const X>;
using B = std::pair<int, X>;
static_assert(std::is_constructible<A, B>::value, "(1)"); // Ok.
static_assert(std::is_convertible<B, A>::value, "(2)"); // Rejected by GCC and Clang, in C 14 and before.
static_assert(std::is_constructible<const X, X>::value, "(3)"); // Ok.
static_assert(std::is_convertible<X, const X>::value, "(4)"); // Ok.
In short, the constructor of std::pair<int, const std::unique_ptr<int>>
from an rvalue of type std::pair<int, std::unique_ptr<int>>
is explicit
in C 14, and implicit and C 17 and newer. Where does this difference come from?
Only libstdc and libc demonstrate this behavior. In MSVC's library, the constructor is always implicit.
Why was it explicit
in C 14? I don't see anything relevant on cppreference (constructor (6)).
CodePudding user response:
The relevant constructors have not been explicit
before C 17 either.
GCC and Clang in pre-C 17 mode are actually considering std::is_convertible<B, A>::value
false because A
's move constructor is implicitly deleted.
The move constructor is implicitly deleted, because a const std::unique_ptr
cannot be moved or copied.
The move constructor is required to fulfill std::is_convertible<B, A>::value
before C 17, because it tests whether a function of the form
A test() {
return std::declval<B>();
}
would be well-formed. The return statement copy-initializes the A
return value from the operand and before C 17 copy-initialization always involved a conversion to a temporary of the target type if necessary, then followed by direct-initialization of the target from the temporary. That direct-initialization would use the move constructor of A
. The move can be elided by the compiler, but the move constructor must still be usable.
Since C 17 mandatory copy elision applies and even conceptually the copy-initialization does not contain the direct-initialization via the move constructor anymore.