Home > Blockchain >  Why explicit-ness of std::pair's heterogeneous "move"-constructor changed in C 17?
Why explicit-ness of std::pair's heterogeneous "move"-constructor changed in C 17?

Time:07-23

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.

  • Related