std :: pair:太限制性构造函数? [英] std::pair: too restrictive constructor?

查看:174
本文介绍了std :: pair:太限制性构造函数?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我偶然发现了使用C ++ 11引入的新的 std :: pair 构造函数的令人惊讶的行为。我使用 std :: pair< int,std :: atomic< int>> 时观察到了这个问题,因为 std ::原子既不可复制也不可移动。在下面的代码中,为了简化,我替换 std :: atomic< int> foobar b
$ b

以下代码使用GCC-4.9和Clang-3.5(带和不带libc ++)编译良好:

  struct foobar 
{
foobar(int){} //隐式转换
// foobar(const foobar&)= delete;
};

std :: pair< int,foobar> p {1,2};

这是预期的行为。但是,当我删除 foobar 的复制构造函数时,编译失败。它适用于分段构造,但我认为不应该是必要的,因为从 int foob​​ar 。我指的是具有以下签名的构造函数:

  template< typename U,typename V& 
pair(U&& u,V&& v);你可以解释一下,为什么对构造函数是这样限制的,并且不允许隐式转换为非可复制的/不可复制的,不可移动类型?

解决方案

这是标准的一个缺陷(我没有找到它, $ c> tuple )。



http://www.open-std.org/jtc1/sc22/wg21/docs/lwg-active.html#2051 p>

进一步讨论和提议的决议(2015年5月在Lenexa投票成C ++ 1z):



http://open-std.org/JTC1/SC22/WG21 /docs/papers/2015/n4387.html






潜在的问题是 pair tuple 检查 is_convertible 移动构造函数。



En细节: std :: pair< T1,T2> code> std :: tuple 看起来像这样:

  V> 
constexpr pair(U&&&&&&&&&

但这太贪婪:当你尝试使用不兼容的类型时,和 std :: is_constructible< pair< T1,T2>,U,V> :: value 将总是 true 因为此构造函数模板的声明可以为任何类型 U V 实例化。因此,我们需要限制这个构造函数模板:

  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 }

注意 tx(forward< A>(a) / code>可以调用 explicit 构造函数。由于 pair 的此构造函数模板未标记为显式,因此必须将其限制为同时初始化其数据成员。因此,我们使用 is_convertible

  template< class U,class V ,
std :: enable_if_t< std :: is_convertible< U&&&&&&&&&&&
std :: is_convertible< V&&&&&&&& amp;> :: value>
>
constexpr pair(U& u,V&& v)
:t1(forward U }

在OP的情况下,没有隐式转换:类型是不可复制的,呈现定义隐式可兑换性的测试:

  // v是任何类型的表达式`int`
foobar f = v; //隐式可转换性的定义

根据标准的这种复制初始化形式在右边产生一个临时用 v

初始化

  foobar f = foobar ); 

其中右边应理解为隐式转换(因此不需要显式构造函数可以调用)。但是,这需要将右侧的临时文件复制或移动到 f (直到C ++ 1z,参见 p0135r0 )。



总结: int 不能隐式转换为 foobar ,因为隐式可转换性被定义,这需要移动性,因为RVO不是强制性的。 pair< int,foobar> 不能从 {1,2} 构造函数模板不是显式,因此需要隐式转换。






更好的解决显式 vs隐式转换问题,如 pair 的改进 显式魔术:


构造函数 explicit 如果且仅当 is_convertible< U&& amp;
first_type> :: value
$ c> false
is_convertible< V&&&&&&" :: value
is / code>。


通过这个更改,我们可以放宽隐式可转换性的限制( is_convertible )转换为显式可转换性( is_constructible )。实际上,在这种情况下,我们得到以下构造函数模板:

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

这是不受限制的 std :: pair< int,foobar> p {1,2}; 有效。


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?

解决方案

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

http://www.open-std.org/jtc1/sc22/wg21/docs/lwg-active.html#2051

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

http://open-std.org/JTC1/SC22/WG21/docs/papers/2015/n4387.html


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.

这篇关于std :: pair:太限制性构造函数?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆