关于pair的构造函数
c++11里 std::pair<T1,T2>::pair
的构造函数有:
constexpr pair();
pair(const T1& x, const T2& y);
template <class U1, class U2> pair(U1&& x, U2&& y);
template <class U1, class U2> pair(const pair<U1, U2>& p);
template <class U1, class U2> pair(pair<U1, U2>&& p);
template <class... Args1, class... Args2>
pair(std::piecewise_construct_t,
std::tuple<Args1...> first_args,
std::tuple<Args2...> second_args);
pair(const pair& p) = default;
pair(pair&& p) = default;
来自stackoverflow的一个问题 “why do i need to use piecewise_construct in map::emplace for single arg constructors of noncopyable objects?” 的解答中说明了 pair 的一些问题,以下内容是一个简述。
#include <map>
struct A
{
A(int) {}
A(A&&) = delete;
A(A const&) = delete;
};
int main()
{
std::pair<int, A> x(1, 4); // error
}
std::pair<int, A> x(1, 4);
期望使用 template <class U1, class U2> pair(U1&& x, U2&& y);
,但 cppreference 对于该构造函数说:
以 std::forward(x) 初始化 first 并以 std::forward(y) 初始化 second 。
此构造函数参与重载决议,当且仅当 std::is_constructible_v<first_type, U1&&> 和 std::is_constructible_v<second_type, U2&&> 均为 true 。
此构造函数为 explicit ,当且仅当 std::is_convertible_v<U1&&, first_type> 为 false 或 std::is_convertible_v<U2&&, second_type> 为 false 。
因为A的移动构造是delete的,所以该版本的构造函数不能使用。
然后去尝试 pair(const T1& x, const T2& y);
,因为A的复制构造也是delete的,这个版本的构造函数也不能用。
所以找不到可用的构造函数了。
这可咋整,注意 pair 的第6个构造函数:
转发 first_args 的元素到 first 的构造函数并转发 second_args 的元素到 second 的构造函数。这是能用于构造不可复制不可移动类型的 pair 的仅有的非默认构造函数。
std::piecewise_construct_t 就是用于这种情形的一个专用类型,std::piecewise_construct是它的一个对象。改为 std::pair<int, A> x(std::piecewise_construct, std::forward_as_tuple(1), std::forward_as_tuple(4));
搞定。
因为 map 的 value_type 是 pair<const K, V>
,map::emplace 会将参数全部转发给 value_type 进行原地构造,所以会出现这种代码:
#include <map>
struct A
{
A(int) {}
A(A&&) = delete;
A(const A&) = delete;
};
int main()
{
std::map<int, A> map;
map.emplace(1, 2); // doesn't work
map.emplace(std::piecewise_construct, std::forward_as_tuple(1), std::forward_as_tuple(2)); // OK
}
另外,对于 copy-initialization(用等号初始化):A t = 4;
,会先创建一个临时的A,然后用这个临时对象初始化t,c++98里是调拷贝构造,c++11则是调移动构造。
但是,看起来只是形式上需要拷贝构造或移动构造(将他们声明为delete或private的将会编译不通过),实际上并没有调用到(通过在内部调用printf测试),或许是编译器进行了优化。
tuple
接下来介绍下 tuple。tuple 是 pair 的推广,可以保存多个不同类型的元素。
有几个函数可以用来创建 tuple:
//返回类型由参数推断而来
template <class... Types>
constexpr tuple<VTypes...> make_tuple(Types&&...);
template<class... Types>
tuple<Types&&...> forward_as_tuple (Types&&... args) noexcept;
template<class... Types>
constexpr tuple<Types&...> tie(Types&...) noexcept;
template <class... Tuples>
constexpr tuple<CTypes...> tuple_cat(Tuples&&...);
std::forward_as_tuple 创建用于转发的 tuple,上节已经用过了。
std::tie 创建到其参数或 std::ignore 实例的左值引用的 tuple,std::ignore 是用 tie 解包 tuple 时用来跳过元素的占位符,例如:
std::map<int,int> m;
bool result;
//tuple可以从pair构造或赋值,如果tuple只有两个元素的话
std::tie(std::ignore, result) = m.insert(std::make_pair(1,2));
std::tuple_cat 可以将多个 tuple 连接成新的 tuple,如:
#include <iostream>
#include <tuple>
#include <string>
template<class Tuple, std::size_t N>
struct TuplePrinter {
static void print(const Tuple& t)
{
TuplePrinter<Tuple, N-1>::print(t);
std::cout << ", " << std::get<N-1>(t);
}
};
template<class Tuple>
struct TuplePrinter<Tuple, 1> {
static void print(const Tuple& t)
{
std::cout << std::get<0>(t);
}
};
template<class... Args>
void print(const std::tuple<Args...>& t)
{
std::cout << "(";
TuplePrinter<decltype(t), sizeof...(Args)>::print(t);
std::cout << ")\n";
}
int main()
{
std::tuple<int, std::string, float> t1(10, "Test", 3.14);
int n = 7;
auto t2 = std::tuple_cat(t1, std::make_tuple("Foo", "bar"), t1, std::tie(n));
n = 10;
print(t2);
}
//(10, Test, 3.14, Foo, bar, 10, Test, 3.14, 10)
上面用到了 std::get,它从 tuple 中提取元素,另外它还能从 std::pair 和 std::array 中提取元素。
std::tuple_size 用来获取 tuple,pair,array 的大小:
#include <iostream>
#include <utility>
#include <tuple>
#include <array>
template<class T>
void test(T t)
{
int a[std::tuple_size<T>::value]; // 可用于编译时
std::cout << std::tuple_size<T>::value << '\n'; // 或运行时
}
int main()
{
test(std::make_tuple(1, 2, 3.14));
test(std::make_pair(1, 3.14));
std::array<float, 3> arr;
test(arr);
}
std::tuple_element 用来获取 tuple,pair,array 元素的类型,如:
#include <iostream>
#include <tuple>
template <class... Args>
struct type_list
{
template <std::size_t N>
using type = typename std::tuple_element<N, std::tuple<Args...>>::type;
};
int main()
{
std::cout << std::boolalpha;
type_list<int, char, bool>::type<2> x = true;
std::cout << x << '\n';
}
参考 <tuple>