C++ 古怪类型与容器的交互

C++允许定义禁止复制和移动的类型,例如:

1
2
3
4
5
6
7
8
9
10
11
class OpaqueData {
public:
OpaqueData() = default;
~OpaqueData() = default;
// Disable copy
OpaqueData(const OpaqueData &) = delete;
auto operator=(const OpaqueData &) -> OpaqueData & = delete;
// Disable move
OpaqueData(OpaqueData &&) = delete;
auto operator=(OpaqueData &&) -> OpaqueData & = delete;
};

对于这种类型,容器的很多需要元素移动的操作都是不合法的。因此需要使用emplace来在容器内就地构造元素。

这对于std::list是没问题的,但是对于map/unordered_map是不足够的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <list>
#include <map>

using namespace std;

auto main() -> int {
std::list<OpaqueData> list;
list.emplace_back(); // ok

std::map<int, OpaqueData> map;
map.emplace(0, {}); // Error

return 0;
}

因为map/unordered_map需要在一次调用构造两个元素keyvalue,因此没办法区分哪些参数用于构造key,哪些用于构造value

为了解决这个问题,需要使用 std::piecewise_constructstd::forward_as_tuple 明确哪些参数构造key/value,将上述代码修改成下面的样子,即可通过编译:

1
2
3
4
5
auto main() -> int {
// ...
map.emplace(std::piecewise_construct, std::forward_as_tuple(0), std::forward_as_tuple()); // Ok
// ...
}

那么这两个东西是什么呢?查看标准库中emplace的代码会发现,这些参数会不断地被forward,最终会原封不动地被用来构造底层元素,而底层元素实际上是std::pair

也就是说,这些奇奇怪怪的参数其实是std::pair的一种构造方式,查看std::pair的构造函数证明了这一点:

1
2
3
4
5
6
/* In stl_pair.h */
/// Tag type for piecewise construction of std::pair objects.
struct piecewise_construct_t { explicit piecewise_construct_t() = default; };

/// Tag for piecewise construction of std::pair objects.
constexpr piecewise_construct_t piecewise_construct = piecewise_construct_t();
1
2
3
4
5
6
/* In tuple */
/// Create a tuple of lvalue or rvalue references to the arguments
template<typename... _Elements>
constexpr tuple<_Elements&&...> forward_as_tuple(_Elements&&... __args) noexcept {
return tuple<_Elements&&...>(std::forward<_Elements>(__args)...);
}
1
2
3
4
5
6
7
8
9
/* In stl_pair.h */
template<class _T1, class _T2>
template<typename... _Args1, typename... _Args2>
_GLIBCXX20_CONSTEXPR
inline
pair<_T1, _T2>::pair(piecewise_construct_t, tuple<_Args1...> __first, tuple<_Args2...> __second) : pair(__first, __second,
typename _Build_index_tuple<sizeof...(_Args1)>::__type(),
typename _Build_index_tuple<sizeof...(_Args2)>::__type())
{ }