std::pair: too restrictive constructor?

c++, c++11

Solution

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.

Problem

I stumbled upon a surprising behaviour of the new `std::pair` constructor, that was introduced with C++11. I observed the issue when using `std::pair<int, std::atomic<int>>`, and it occurs, because `std::atomic` is neither copyable nor movable. In the following code, I replace `std::atomic<int>` with `foobar` for simplification. The following code compiles fine, both with GCC-4.9 and Clang-3.5 (with and without libc++): ``` struct foobar { foobar(int) { } // implicit conversion // foobar(const foobar&) = delete; }; std::pair<int, foobar> p{1, 2}; ``` This behaviour is expected. However, when I delete the copy constructor of `foobar`, the compilation fails. It works with piecewise construct, but I think that shouldn't be necessary, because of the implicit conversion from `int` to `foobar`. I am referring to the constructor with the following signature: ``` template <typename U, typename V> pair(U&& u, V&& v); ``` Can you explain, why the pair constructor is so restrictive, and does not allow implicit conversions for noncopyable/nonmovable types?

Original source

Related problems