std::vector 构造函数为何使用 std::type_identity?
std::type_identity_t<Allocator> 用于避免类模板实参推导(CTAD)中的冲突:第一个参数 other 已经能够确定 std::vector 的元素类型和 allocator 类型,第二个参数只需能转换为该 allocator 类型,无须再次参与推导。这也是 P1518R2 提案 修改这些构造函数的原因。
构造函数为什么会涉及模板实参推导?
如果写出完整类型,例如 std::vector<int, MyAllocator>,类模板参数就已经确定了。
下面的写法则使用了 CTAD:
std::vector copied(source, alloc);
编译器需要先确定这里的 std::vector 是哪个特化。即使相应构造函数本身不是函数模板,CTAD 也会根据它生成隐式推导指引。具体规则见 C++ 标准草案的类模板实参推导章节。
旧声明的相关推导过程可以简化为:
template<class T, class Allocator>
auto deduce(const std::vector<T, Allocator>&,
const Allocator&)
-> std::vector<T, Allocator>;
这个示意声明中,两个参数都会参与 Allocator 的推导。
什么情况下会发生冲突?
一种情况是传入 std::pmr::memory_resource 的指针,而非已经构造好的 std::pmr::polymorphic_allocator<int>:
#include <memory_resource>
#include <type_traits>
#include <utility>
#include <vector>
int main()
{
std::pmr::monotonic_buffer_resource resource;
std::pmr::vector<int> source({1, 2, 3}, &resource);
// 在实现了相关 C++23 修改的标准库中有效。
std::vector copied(source, &resource);
std::vector moved(std::move(source), &resource);
static_assert(std::is_same_v<decltype(copied),
std::pmr::vector<int>>);
static_assert(std::is_same_v<decltype(moved),
std::pmr::vector<int>>);
}
按照旧声明推导 copied 时,两个实参分别给出:
- 从
source推导:T = int,Allocator = std::pmr::polymorphic_allocator<int>。 - 从
&resource推导:Allocator = std::pmr::monotonic_buffer_resource*。
同一个 Allocator 被推导为不同的类型,因此推导失败。模板实参推导不会先把指针隐式转换成 allocator,再尝试匹配。这与 foo(1.0, 0) 分别推导出 T = double 和 T = int 的冲突相对应。
类型确定后,这个指针可以作为构造实参:std::pmr::polymorphic_allocator<int> 的构造函数允许从 memory_resource* 隐式转换,派生资源类的指针也可以先转换为该基类指针。参见 polymorphic_allocator 的构造函数规定。
std::type_identity_t 如何解决问题?
修改后的推导过程可以简化为:
template<class T, class Allocator>
auto deduce(const std::vector<T, Allocator>&,
const std::type_identity_t<Allocator>&)
-> std::vector<T, Allocator>;
std::type_identity_t<Allocator> 最终仍表示 Allocator,但这里形成了非推导上下文,第二个参数不再用于推导 Allocator。
编译器先从 source 确定 T 和 Allocator,再检查 &resource 能否转换为已经确定的 allocator 类型。转换合法,构造就可以继续。std::type_identity 不会创建新的 allocator 类型;它的 type 成员表示原类型,见 标准草案中的类型变换规定。
实际使用时如何处理?
如果标准库尚未实现这项修改,可以显式构造 allocator,使第二个实参的类型与第一个实参中的 allocator 类型一致:
std::vector copied(
source,
std::pmr::polymorphic_allocator<int>{&resource}
);
也可以直接指定目标容器类型,避免 CTAD:
std::pmr::vector<int> copied(source, &resource);
这项修改不会让这些构造函数接受任意 allocator 类型的源容器;源容器仍须符合对应 std::vector<T, Allocator> 特化的要求。另外,示例中的 resource 必须在使用它的容器销毁之前保持有效。
备注:内容仅供参考。
