std :: pair:太限制性构造函数? [英] std::pair: too restrictive constructor?
问题描述
我偶然发现了使用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
到 foobar
。我指的是具有以下签名的构造函数:
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;
$ c> false 或
first_type> :: valueis_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 usingstd::pair<int, std::atomic<int>>
, and it occurs, becausestd::atomic
is neither copyable nor movable. In the following code, I replacestd::atomic<int>
withfoobar
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 fromint
tofoobar
. 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
andtuple
check foris_convertible
which requires an accessible copy/move constructor.En detail: The converting constructor templates of
std::pair<T1, T2>
andstd::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 betrue
because the declaration of this constructor template can be instantiated for any typesU
andV
. 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 callexplicit
constructors. Because this constructor template ofpair
is not marked as explicit, we must restrict it to not perform explicit conversions internally while initializing its data members. Therefore, we useis_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 intof
(until C++1z, see p0135r0).To sum up:
int
is not implicitly convertible tofoobar
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 thispair
constructor template is notexplicit
and hence requires implicit conversions.
A better solution to the
explicit
vs implicit conversion problem as presented in Improvements onpair
andtuple
is to haveexplicit
magic:The constructor is
explicit
if and only ifis_convertible<U&&, first_type>::value
isfalse
oris_convertible<V&&, second_type>::value
isfalse
.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屋!