我偶然发现了新的std::pair
构造函数的令人惊讶的行为,这是C ++ 11引入的。我在使用std::pair<int, std::atomic<int>>
时发现了这个问题,并且它发生了,因为std::atomic
既不可复制也不可移动。在以下代码中,为了简化,我将std::atomic<int>
替换为foobar
。
下面的代码编译很好,包括GCC-4.9和Clang-3.5(有和没有libc ++):
struct foobar
{
foobar(int) { } // implicit conversion
// foobar(const foobar&) = delete;
};
std::pair<int, foobar> p{1, 2};
此行为是预期的。但是,当我删除foobar
的复制构造函数时,编译失败。它适用于分段构造,但我认为不应该这样做,因为从int
隐式转换为foobar
。我指的是具有以下签名的构造函数:
template <typename U, typename V>
pair(U&& u, V&& v);
你能解释为什么对构造函数是如此限制,并且不允许对noncopyable / nonmovable类型进行隐式转换?
答案 0 :(得分:9)
这是标准中的一个缺陷(我最初没有找到它,因为它是为tuple
制定的。)
进一步讨论和提议的决议(2015年5月在Lenexa投票选入C ++ 1z):
根本问题是pair
和tuple
的转换构造函数检查is_convertible
,这需要一个可访问的复制/移动构造函数。
详细信息:std::pair<T1, T2>
和std::tuple
的转换构造函数模板如下所示:
template<class U, class V>
constexpr pair(U&&, V&&);
但这太贪婪了:当你尝试将它与不兼容的类型一起使用时会产生一个硬错误,std::is_constructible<pair<T1, T2>, U, V>::value
总是true
,因为这个构造函数模板的声明可以实例化为< em>任何类型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>(u) ), t2( forward<V>(v) )
{}
请注意,tx( forward<A>(a) )
可以调用explicit
构造函数。由于此pair
的构造函数模板未标记为显式,因此我们必须将其限制为不在内部执行显式转换,同时初始化数据成员。因此,我们使用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) )
{}
在OP的情况下,没有隐式转换:类型是不可复制的,这会导致定义隐式转换错误形成的测试:
// v is any expression of type `int`
foobar f = v; // definition of implicit convertibility
根据标准的这个复制初始化表单在右侧生成一个临时表,用v
初始化:
foobar f = foobar(v);
右侧应被理解为隐式转换(因此不能调用explicit
构造函数)。但是,这需要将右侧的临时文件复制或移动到f
(直到C ++ 1z,请参阅p0135r0)。
总结:int
不能隐式转换为foobar
,因为定义了隐式可转换性,这需要可移动性,因为RVO不是必需的。无法从pair<int, foobar>
构建{1, 2}
,因为此pair
构造函数模板不是explicit
,因此需要隐式转换。
Improvements on pair
and tuple
中提到的explicit
vs隐式转换问题的更好解决方案是explicit
魔术:
当且仅当
explicit
为is_convertible<U&&, first_type>::value
或false
时,构造函数为is_convertible<V&&, second_type>::value
是false
。
通过此更改,我们可以放宽隐式可兑换性(is_convertible
)对“显式可兑换性”(is_constructible
)的限制。实际上,在这种情况下,我们得到以下构造函数模板:
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&&);
哪个不受限制,足以让std::pair<int, foobar> p{1, 2};
有效。
答案 1 :(得分:1)
测试你的代码,删除了复制构造函数,我得到了
[h:\dev\test\0082] > g++ foo.cpp In file included from h:\bin\mingw\include\c++\4.8.2\utility:70:0, from foo.cpp:1: h:\bin\mingw\include\c++\4.8.2\bits\stl_pair.h: In instantiation of 'constexpr std::pair::pair(_U1&&, const _T2&) [with _U1 = int; <template-parameter-2-2> = void; _T1 = int; _T2 = foobar]': foo.cpp:12:34: required from here h:\bin\mingw\include\c++\4.8.2\bits\stl_pair.h:134:45: error: use of deleted function 'foobar::foobar(const foobar&)' : first(std::forward<_U1>(__x)), second(__y) { } ^ foo.cpp:6:5: error: declared here foobar(const foobar&) = delete; ^ [h:\dev\test\0082] > cl foo.cpp foo.cpp [h:\dev\test\0082] > _
提到的构造函数
pair(_U1&&, const _T2&)
未由标准指定。
附录:如下所示,代码只适用于为对类定义的标准构造函数:
#include <utility>
struct foobar
{
foobar(int) { } // implicit conversion
foobar(const foobar&) = delete;
};
namespace bah {
using std::forward;
using std::move;
struct Piecewise_construct_t {};
template <class T1, class T2>
struct Pair {
typedef T1 first_type;
typedef T2 second_type;
T1 first;
T2 second;
//Pair(const Pair&) = default;
//Pair(Pair&&) = default;
/*constexpr*/ Pair(): first(), second() {}
Pair(const T1& x, const T2& y)
: first( x ), second( y )
{}
template<class U, class V> Pair(U&& x, V&& y)
: first( forward<U>( x ) ), second( forward<V>( y ) )
{}
template<class U, class V> Pair(const Pair<U, V>& p)
: first( p.first ), second( p.second )
{}
template<class U, class V> Pair(Pair<U, V>&& p)
: first( move( p.first ) ), second( move( p.second ) )
{}
//template <class... Args1, class... Args2>
//Pair(Piecewise_construct_t,
//tuple<Args1...> first_args, tuple<Args2...> second_args);
//
//Pair& operator=(const Pair& p);
//template<class U, class V> Pair& operator=(const Pair<U, V>& p);
//Pair& operator=(Pair&& p) noexcept(see below);
//template<class U, class V> Pair& operator=(Pair<U, V>&& p);
//void swap(Pair& p) noexcept(see below);
};
}
auto main()
-> int
{
bah::Pair<int, foobar> p{1, 2};
};
[h:\dev\test\0082] > g++ bar.cpp [h:\dev\test\0082] > _
重要错误。
正如@dyb在注释中指出的那样,而标准的“requires”子句引用std::is_constructible
(该对的项必须可以从参数构造),“remarks”子句遵循Defect Report 811的解析,指可兑换性:
C ++11§20.3.2/ 8:
“备注:如果U
无法隐式转换为first_type
或V
不能隐式转换为second_type
,则此构造函数不得参与重载解析。”
所以,虽然这可能是标准中的缺陷,但从形式上看,代码不应该编译。