std::pair: too restrictive constructor?

It's a defect in the Standard (I didn't found it at first since it's formulated for tuple).

https://wg21.link/lwg2051

Further discussion and a proposed resolution (voted into C++1z at Lenexa in May 2015):

https://wg21.link/n4387


The underlying problem is that the converting constructors of pair and tuple check for is_convertible which requires an accessible copy/move constructor.

En detail: The converting constructor templates of std::pair<T1, T2> and std::tuple look like this:

template<class U, class V>
constexpr pair(U&&, V&&);

But this is too greedy: It produces a hard error when you try to use it with incompatible types, and std::is_constructible<pair<T1, T2>, U, V>::value will always be true because the declaration of this constructor template can be instantiated for any types U and V. Hence, we need to restrict this constructor template:

template<class U, class V,
    enable_if_t<check_that_we_can_construct_from<U, V>::value>
>
constexpr pair(U&& u, V&& v)
    : t1( forward<U>(u) ), t2( forward<V>(v) )
{}

Note that the tx( forward<A>(a) ) can call explicit constructors. Because this constructor template of pair is not marked as explicit, we must restrict it to not perform explicit conversions internally while initializing its data members. Therefore, we use is_convertible:

template<class U, class V,
    std::enable_if_t<std::is_convertible<U&&, T1>::value &&
                     std::is_convertible<V&&, T2>::value>
>
constexpr pair(U&& u, V&& v)
    : t1( forward<U>(u) ), t2( forward<V>(v) )
{}

In the case of the OP, there is no implicit conversion: the type is noncopyable, and this renders the test that defines implicit convertibility ill-formed:

// v is any expression of type `int`
foobar f = v; // definition of implicit convertibility

This copy-initialization form according to the Standard produces a temporary on the right hand side, initialized with v:

foobar f = foobar(v);

Where the right hand side shall be understood as an implicit conversion (so no explicit constructors can be called). However, this requires to copy or move the temporary on the right hand side into f (until C++1z, see p0135r0).

To sum up: int is not implicitly convertible to foobar because of the way implicit convertibility is defined, which requires moveability because RVO is not mandatory. pair<int, foobar> cannot be constructed from {1, 2} because this pair constructor template is not explicit and hence requires implicit conversions.


A better solution to the explicit vs implicit conversion problem as presented in Improvements on pair and tuple is to have explicit magic:

The constructor is explicit if and only if is_convertible<U&&, first_type>::value is false or is_convertible<V&&, second_type>::value is false.

With this change, we can loosen the restriction of implicit convertibility (is_convertible) to "explicit convertibility" (is_constructible). Effectively, we get the following constructor template in this case:

template<class U, class V,
    std::enable_if_t<std::is_constructible<U&&, int>::value &&
                     std::is_constructible<V&&, foobar>::value>
>
explicit constexpr pair(U&&, V&&);

Which is unrestricted enough to make std::pair<int, foobar> p{1, 2}; valid.

Tags:

C++

C++11