PAYATHON 2026

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 必须在使用它的容器销毁之前保持有效。

备注:内容仅供参考。