一般来说,像括号初始化列表这样{}
的不是表达式,也没有类型。如果你有一个函数模板
template<typename T> void f(T);
和 call f( {} )
,不会为 推导T
类型,类型推导失败。
另一方面,ABC{}
是类型的纯右值表达式ABC
(“函数符号中的显式类型转换”)。对于类似的调用f( ABC{} )
,函数模板可以ABC
从这个表达式中推断出类型。
在 C++14 和 C++11 中,std::pair
具有以下构造函数 [pairs.pair];T1
并且是类模板T2
的模板参数的名称:std::pair
pair(const pair&) = default;
pair(pair&&) = default;
constexpr pair();
constexpr pair(const T1& x, const T2& y);
template<class U, class V> constexpr pair(U&& x, V&& y);
template<class U, class V> constexpr pair(const pair<U, V>& p);
template<class U, class V> constexpr pair(pair<U, V>&& p);
template <class... Args1, class... Args2>
pair(piecewise_construct_t, tuple<Args1...>, tuple<Args2...>);
注意有一个构造函数
constexpr pair(const T1& x, const T2& y); // (C)
但不是
constexpr pair(T1&& x, T2&& y);
相反,有一个完美的转发
template<class U, class V> constexpr pair(U&& x, V&& y); // (P)
如果您尝试std::pair
使用两个初始化程序来初始化 a,其中至少一个是花括号初始化列表,则构造函数 (P) 不可行,因为它无法推断其模板参数。
(C) 不是构造函数模板。它的参数类型由T1 const&
类T2 const&
模板参数固定。可以从空的括号初始化列表初始化对常量类型的引用。这将创建一个绑定到引用的临时对象。由于引用的类型是 const,因此 (C) 构造函数会将其参数复制到类的数据成员中。
当您通过 初始化一对时std::pair<T,U>{ T{}, U{} }
,T{}
和U{}
是纯右值表达式。构造函数模板(P)可以推导出它们的类型并且是可行的。类型推导后产生的实例化比 (C) 构造函数更匹配,因为 (P) 将产生右值引用参数并将纯右值参数绑定到它们。(C) 另一方面,将纯右值参数绑定到左值引用。
那么为什么现场示例在调用时会移动第二个参数 via std::pair<T,U>{ {}, U{} }
?
libstdc++ 定义了额外的构造函数。下面是std::pair
从 78536ab78e 中摘录的实现,省略了函数定义、一些注释和 SFINAE。_T1
和是类模板_T2
的模板参数的名称。std::pair
_GLIBCXX_CONSTEXPR pair();
_GLIBCXX_CONSTEXPR pair(const _T1& __a, const _T2& __b); // (C)
template<class _U1, class _U2>
constexpr pair(const pair<_U1, _U2>& __p);
constexpr pair(const pair&) = default;
constexpr pair(pair&&) = default;
// DR 811.
template<class _U1>
constexpr pair(_U1&& __x, const _T2& __y); // (X)
template<class _U2>
constexpr pair(const _T1& __x, _U2&& __y); // (E) <=====================
template<class _U1, class _U2>
constexpr pair(_U1&& __x, _U2&& __y); // (P)
template<class _U1, class _U2>
constexpr pair(pair<_U1, _U2>&& __p);
template<typename... _Args1, typename... _Args2>
pair(piecewise_construct_t, tuple<_Args1...>, tuple<_Args2...>);
注意 (E) 构造函数模板:它将复制第一个参数并完美转发第二个参数。对于像这样的初始化std::pair<T,U>{ {}, U{} }
,它是可行的,因为它只需要从第二个参数推导出一个类型。对于第二个参数,它也比 (C) 更好的匹配,因此总体上更好的匹配。
“DR 811”注释在 libstdc++ 源代码中。它指的是LWG DR 811,它添加了一些 SFINAE,但没有新的构造函数。
构造函数 (E) 和 (X) 是 libstdc++ 扩展。不过,我不确定它是否合规。
另一方面,libc++ 没有这个额外的构造函数。例如std::pair<T,U>{ {}, U{} }
,它将复制第二个参数。
两个库实现的现场演示