命名空间
变体
操作

类模板参数推导 (CTAD) (自 C++17 起)

来自 cppreference.com
< cpp‎ | 语言
 
 
C++ 语言
通用主题
流程控制
条件执行语句
if
迭代语句(循环)
for
基于范围的 for (C++11)
跳转语句
函数
函数声明
Lambda 函数表达式
inline 说明符
动态异常规范 (直到 C++17*)
noexcept 说明符 (C++11)
异常
命名空间
类型
说明符
const/volatile
decltype (C++11)
auto (C++11)
constexpr (C++11)
consteval (C++20)
constinit (C++20)
存储期说明符
初始化
表达式
替代表示形式
字面量
布尔型 - 整型 - 浮点型
字符型 - 字符串型 - nullptr (C++11)
用户定义 (C++11)
实用程序
属性 (C++11)
类型
typedef 声明
类型别名声明 (C++11)
强制转换
内存分配
类特定函数属性
虚函数
override 说明符 (C++11)  
final 说明符 (C++11)
explicit (C++11)
static

特殊成员函数
模板
模板特化
参数包 (C++11)
杂项
 
 
 
 

为了实例化 类模板,必须知道每个模板参数,但并非每个模板参数都必须指定。在以下上下文中,编译器将从初始化器的类型推导出模板参数

  • 任何指定变量和变量模板初始化的 声明,其声明类型是类模板(可能 cv 限定
std::pair p(2, 4.5);     // deduces to std::pair<int, double> p(2, 4.5);
std::tuple t(4, 3, 2.5); // same as auto t = std::make_tuple(4, 3, 2.5);
std::less l;             // same as std::less<void> l;
template<class T>
struct A
{
    A(T, T);
};
 
auto y = new A{1, 2}; // allocated type is A<int>
auto lck = std::lock_guard(mtx);     // deduces to std::lock_guard<std::mutex>
std::copy_n(vi1, 3,
    std::back_insert_iterator(vi2)); // deduces to std::back_insert_iterator<T>,
                                     // where T is the type of the container vi2
std::for_each(vi.begin(), vi.end(),
    Foo([&](int i) {...}));          // deduces to Foo<T>,
                                     // where T is the unique lambda type
template<class T>
struct X
{
    constexpr X(T) {}
};
 
template<X x>
struct Y {};
 
Y<0> y; // OK, Y<X<int>(0)>
(自 C++20 起)

目录

[编辑] 类模板的推导

[编辑] 隐式生成的推导指南

当在函数风格转换或变量的声明中,类型说明符仅包含主类模板 C 的名称(即,没有附带的模板参数列表)时,推导的候选对象如下形成

  • 如果定义了 C,则对于在命名主模板中声明的每个构造函数(或构造函数模板)Ci,都会构造一个虚构的函数模板 Fi,以满足以下所有条件
  • Fi 的模板参数是 C 的模板参数,后跟(如果 Ci 是构造函数模板)Ci 的模板参数(也包括默认模板参数)。
  • Fi关联约束C 的关联约束和 Ci 的关联约束的合取。
(自 C++20 起)
  • Fi参数列表Ci 的参数列表。
  • Fi 的返回类型是 C,后跟括在 <> 中的类模板的模板参数。
  • 如果未定义 C 或未声明任何构造函数,则会添加一个额外的虚构函数模板,该模板如上所述从假设的构造函数 C() 派生。
  • 在任何情况下,都会添加一个如上所述从假设的构造函数 C(C) 派生的额外虚构函数模板,称为复制推导候选对象。
  • Fi 的参数列表是 Gi 的参数列表。
  • Fi 的返回类型是 Gi 的简单模板标识符。
  • 如果 Gi 具有模板参数(语法 (2)),则 Fi 是函数模板,其模板参数列表是 Gi 的模板参数列表。否则,Fi 是一个函数。
  • 此外,如果
  • 定义了 C 并满足 聚合类型 的要求,假设任何依赖基类都没有虚函数或虚基类,
  • 没有针对 C 的用户定义的推导指南,并且
  • 变量是从非空的初始化器列表 arg1, arg2, ..., argn 初始化的(可以使用 指定初始化器),
可以添加聚合推导候选。聚合推导候选的参数列表由聚合元素类型生成,如下所示
  • ei 为(可能是递归的)聚合元素,它将从 argi 初始化,其中
  • 如果 C(或其本身是聚合的元素)具有作为包扩展的基类
  • 如果包扩展是尾随聚合元素,则认为它匹配初始化列表的所有剩余元素;
  • 否则,该包被认为是空的。
  • 如果没有这样的 ei,则不添加聚合推导候选。
  • 否则,如下确定聚合推导候选的参数列表 T1, T2, ..., Tn
  • 如果 ei 是数组并且 argi花括号初始化列表,则 Ti 是对 ei 的声明类型的右值引用。
  • 如果 ei 是数组并且 argi字符串字面量,则 Ti 是对 ei 的 const 限定声明类型的左值引用。
  • 否则,Tiei 的声明类型。
  • 如果跳过了一个包,因为它是一个非尾随聚合元素,则在其原始聚合元素位置插入一个形式为 Pj ... 的附加参数包。(这通常会导致推导失败。)
  • 如果一个包是尾随聚合元素,则与其对应的尾随参数序列将被替换为形式为 Tn ... 的单个参数。
聚合推导候选是从假设构造函数 C(T1, T2, ..., Tn) 派生的虚构函数模板。
在对聚合推导候选进行模板参数推导期间,只有在没有其他方式推导出尾随参数包中的元素数量时,才会从剩余函数参数的数量中推导出它。
template<class T>
struct A
{
    T t;
 
    struct
    {
        long a, b;
    } u;
};
 
A a{1, 2, 3};
// aggregate deduction candidate:
//   template<class T>
//   A<T> F(T, long, long);
 
template<class... Args>
struct B : std::tuple<Args...>, Args... {};
 
B b{std::tuple<std::any, std::string>{}, std::any{}};
// aggregate deduction candidate:
//   template<class... Args>
//   B<Args...> F(std::tuple<Args...>, Args...);
 
// type of b is deduced as B<std::any, std::string>
(自 C++20 起)

然后对假设类类型的虚构对象的初始化执行模板参数推导重载决议,其构造函数签名与用于形成重载集的指南(返回类型除外)匹配,并且初始化程序由执行类模板参数推导的上下文提供,但如果初始化列表由类型(可能为 cv 限定)U 的单个表达式组成,则省略列表初始化的第一阶段(考虑初始化列表构造函数),其中 UC 的特化或派生自 C 的特化的类。

这些虚构的构造函数是假设类类型的公共成员。如果指南是由显式构造函数形成的,则它们是显式的。如果重载决议失败,则程序格式错误。否则,所选 F 模板特化的返回类型将成为推导出的类模板特化。

template<class T>
struct UniquePtr
{
    UniquePtr(T* t);
};
 
UniquePtr dp{new auto(2.0)};
 
// One declared constructor:
// C1: UniquePtr(T*);
 
// Set of implicitly-generated deduction guides:
 
// F1: template<class T>
//     UniquePtr<T> F(T* p);
 
// F2: template<class T> 
//     UniquePtr<T> F(UniquePtr<T>); // copy deduction candidate
 
// imaginary class to initialize:
// struct X
// {
//     template<class T>
//     X(T* p);         // from F1
//     
//     template<class T>
//     X(UniquePtr<T>); // from F2
// };
 
// direct-initialization of an X object
// with "new double(2.0)" as the initializer
// selects the constructor that corresponds to the guide F1 with T = double
// For F1 with T=double, the return type is UniquePtr<double>
 
// result:
// UniquePtr<double> dp{new auto(2.0)}

或者,对于更复杂的示例(注意:“S::N” 无法编译:作用域解析限定符不是可以推导的内容)

template<class T>
struct S
{
    template<class U>
    struct N
    {
        N(T);
        N(T, U);
 
        template<class V>
        N(V, U);
    };
};
 
S<int>::N x{2.0, 1};
 
// the implicitly-generated deduction guides are (note that T is already known to be int)
 
// F1: template<class U>
//     S<int>::N<U> F(int);
 
// F2: template<class U>
//     S<int>::N<U> F(int, U);
 
// F3: template<class U, class V>
//     S<int>::N<U> F(V, U);
 
// F4: template<class U>
//     S<int>::N<U> F(S<int>::N<U>); (copy deduction candidate)
 
// Overload resolution for direct-list-init with "{2.0, 1}" as the initializer
// chooses F3 with U=int and V=double.
// The return type is S<int>::N<int>
 
// result:
// S<int>::N<int> x{2.0, 1};

[编辑] 用户定义的推导指南

用户定义的推导指南的语法是带有尾随返回类型的函数(模板)声明的语法,只是它使用类模板的名称作为函数名称

explicit (可选) template-name ( parameter-list ) requires-clause (可选) -> simple-template-id ; (1)
template <template-parameter-list > requires-clause (可选)
explicit (可选) template-name ( parameter-list ) requires-clause (可选) -> simple-template-id ;
(2)
template-parameter-list - 非空的逗号分隔的模板参数列表
explicit - explicit 说明符
template-name - 要推导其参数的类模板的名称
parameter-list - (可能为空的)参数列表
requires-clause - (自 C++20 起) requires-clause
simple-template-id - 简单模板标识符


用户定义的推导指南的参数不能具有占位符类型:不允许使用简化的函数模板语法。

(自 C++20 起)

用户定义的推导指南必须命名一个类模板,并且必须在类模板的相同语义作用域(可以是命名空间或封闭类)内引入,并且对于成员类模板,必须具有相同的访问权限,但推导指南不会成为该作用域的成员。

推导指南不是函数,也没有主体。推导指南不会通过名称查找找到,并且不参与重载决议,除非在推导类模板参数时与其他推导指南进行重载决议。推导指南不能在同一翻译单元中为同一类模板重新声明。

// declaration of the template
template<class T>
struct container
{
    container(T t) {}
 
    template<class Iter>
    container(Iter beg, Iter end);
};
 
// additional deduction guide
template<class Iter>
container(Iter b, Iter e) -> container<typename std::iterator_traits<Iter>::value_type>;
 
// uses
container c(7); // OK: deduces T=int using an implicitly-generated guide
std::vector<double> v = {/* ... */};
auto d = container(v.begin(), v.end()); // OK: deduces T=double
container e{5, 6}; // Error: there is no std::iterator_traits<int>::value_type

如果虚构的构造函数(如上所述)对应于由显式构造函数形成的隐式生成的推导指南或声明为显式的用户定义的推导指南,则它们是显式的。与往常一样,在复制初始化上下文中会忽略此类构造函数

template<class T>
struct A
{
    explicit A(const T&, ...) noexcept; // #1
    A(T&&, ...);                        // #2
};
 
int i;
A a1 = {i, i}; // error: cannot deduce from rvalue reference in #2,
               // and #1 is explicit, and not considered in copy-initialization.
A a2{i, i};    // OK, #1 deduces to A<int> and also initializes
A a3{0, i};    // OK, #2 deduces to A<int> and also initializes
A a4 = {0, i}; // OK, #2 deduces to A<int> and also initializes
 
template<class T>
A(const T&, const T&) -> A<T&>; // #3
 
template<class T>
explicit A(T&&, T&&)  -> A<T>;  // #4
 
A a5 = {0, 1}; // error: #3 deduces to A<int&>
               // and #1 & #2 result in same parameter constructors.
A a6{0, 1};    // OK, #4 deduces to A<int> and #2 initializes
A a7 = {0, i}; // error: #3 deduces to A<int&>
A a8{0, i};    // error: #3 deduces to A<int&>

在构造函数或构造函数模板的参数列表中使用成员 typedef 或别名模板本身不会使隐式生成的指南的相应参数成为非推导上下文。

template<class T>
struct B
{
    template<class U>
    using TA = T;
 
    template<class U>
    B(U, TA<U>); // #1
};
 
// Implicit deduction guide generated from #1 is the equivalent of
//     template<class T, class U>
//     B(U, T) -> B<T>;
// rather than
//     template<class T, class U>
//     B(U, typename B<T>::template TA<U>) -> B<T>;
// which would not have been deducible
 
B b{(int*)0, (char*)0}; // OK, deduces B<char*>

别名模板的推导

当函数样式转换或变量声明使用别名模板 A 的名称而不使用参数列表作为类型说明符时,其中 A 定义为 B<ArgList> 的别名,B 的作用域是非依赖的,并且 B 是类模板或类似定义的别名模板,则推导将以与类模板相同的方式进行,只是指南是从 B 的指南生成的,如下所示

  • 对于 B 的每个指南 f,使用模板参数推导B<ArgList> 推导 f 的返回类型的模板参数,但如果某些参数未被推导,则推导不会失败。如果由于其他原因推导失败,则继续使用一组空的推导模板参数。
  • 将上述推导的结果代入 f,如果替换失败,则不生成指南;否则,令 g 表示替换的结果,则形成指南 f',使得
  • f' 的参数类型和返回类型与 g 相同
  • 如果 f 是一个模板,则 f' 是一个函数模板,其模板参数列表由 A 的所有出现在上述推导中或(递归地)出现在其默认模板参数中的模板参数(包括它们的默认模板参数)组成,后跟 f 中未被推导出的模板参数(包括它们的默认模板参数);否则(f 不是模板),f' 是一个函数
  • f' 的关联约束g 的关联约束和一个当且仅当可以从结果类型推导出 A 的参数时才满足的约束的合取
template<class T>
class unique_ptr
{
    /* ... */
};
 
template<class T>
class unique_ptr<T[]>
{
    /* ... */
};
 
template<class T>
unique_ptr(T*) -> unique_ptr<T>;   // #1
 
template<class T>
unique_ptr(T*) -> unique_ptr<T[]>; // #2
 
template<class T>
concept NonArray = !std::is_array_v<T>;
 
template<NonArray A>
using unique_ptr_nonarray = unique_ptr<A>;
 
template<class A>
using unique_ptr_array = unique_ptr<A[]>;
 
// generated guide for unique_ptr_nonarray:
 
// from #1 (deduction of unique_ptr<T> from unique_ptr<A> yields T = A):
// template<class A>
//     requires(argument_of_unique_ptr_nonarray_is_deducible_from<unique_ptr<A>>)
// auto F(A*) -> unique_ptr<A>;
 
// from #2 (deduction of unique_ptr<T[]> from unique_ptr<A> yields nothing):
// template<class T>
//     requires(argument_of_unique_ptr_nonarray_is_deducible_from<unique_ptr<T[]>>)
// auto F(T*) -> unique_ptr<T[]>;
 
// where argument_of_unique_ptr_nonarray_is_deducible_from can be defined as
 
// template<class>
// class AA;
 
// template<NonArray A>
// class AA<unique_ptr_nonarray<A>> {};
 
// template<class T>
// concept argument_of_unique_ptr_nonarray_is_deducible_from =
//     requires { sizeof(AA<T>); };
 
// generated guide for unique_ptr_array:
 
// from #1 (deduction of unique_ptr<T> from unique_ptr<A[]> yields T = A[]):
// template<class A>
//     requires(argument_of_unique_ptr_array_is_deducible_from<unique_ptr<A[]>>)
// auto F(A(*)[]) -> unique_ptr<A[]>;
 
// from #2 (deduction of unique_ptr<T[]> from unique_ptr<A[]> yields T = A):
// template<class A>
//     requires(argument_of_unique_ptr_array_is_deducible_from<unique_ptr<A[]>>)
// auto F(A*) -> unique_ptr<A[]>;
 
// where argument_of_unique_ptr_array_is_deducible_from can be defined as
 
// template<class>
// class BB;
 
// template<class A>
// class BB<unique_ptr_array<A>> {};
 
// template<class T>
// concept argument_of_unique_ptr_array_is_deducible_from =
//     requires { sizeof(BB<T>); };
 
// Use:
unique_ptr_nonarray p(new int); // deduced to unique_ptr<int>
// deduction guide generated from #1 returns unique_ptr<int>
// deduction guide generated from #2 returns unique_ptr<int[]>, which is ignored because
//   argument_of_unique_ptr_nonarray_is_deducible_from<unique_ptr<int[]>> is unsatisfied
 
unique_ptr_array q(new int[42]); // deduced to unique_ptr<int[]>
// deduction guide generated from #1 fails (cannot deduce A in A(*)[] from new int[42])
// deduction guide generated from #2 returns unique_ptr<int[]>
(自 C++20 起)

[编辑] 注释

仅当不存在模板参数列表时才会执行类模板参数推导。如果指定了模板参数列表,则不会进行推导。

std::tuple t1(1, 2, 3);                // OK: deduction
std::tuple<int, int, int> t2(1, 2, 3); // OK: all arguments are provided
 
std::tuple<> t3(1, 2, 3);    // Error: no matching constructor in tuple<>.
                             //        No deduction performed.
std::tuple<int> t4(1, 2, 3); // Error

聚合的类模板参数推导通常需要用户定义的推导指南

template<class A, class B>
struct Agg
{
    A a;
    B b;
};
// implicitly-generated guides are formed from default, copy, and move constructors
 
template<class A, class B>
Agg(A a, B b) -> Agg<A, B>;
// ^ This deduction guide can be implicitly generated in C++20
 
Agg agg{1, 2.0}; // deduced to Agg<int, double> from the user-defined guide
 
template<class... T>
array(T&&... t) -> array<std::common_type_t<T...>, sizeof...(T)>;
auto a = array{1, 2, 5u}; // deduced to array<unsigned, 3> from the user-defined guide
(直到 C++20)

用户定义的推导指南不必是模板

template<class T>
struct S
{
    S(T);
};
S(char const*) -> S<std::string>;
 
S s{"hello"}; // deduced to S<std::string>

在类模板的作用域内,不带参数列表的模板名称是一个注入的类名,可以用作类型。在这种情况下,不会发生类参数推导,必须显式提供模板参数

template<class T>
struct X
{
    X(T) {}
 
    template<class Iter>
    X(Iter b, Iter e) {}
 
    template<class Iter>
    auto foo(Iter b, Iter e)
    {
        return X(b, e); // no deduction: X is the current X<T>
    }
 
    template<class Iter>
    auto bar(Iter b, Iter e)
    {
        return X<typename Iter::value_type>(b, e); // must specify what we want
    }
 
    auto baz()
    {
        return ::X(0); // not the injected-class-name; deduced to be X<int>
    }
};

重载决议中,偏序优先于函数模板是否从用户定义的推导指南生成:如果从构造函数生成的函数模板比从用户定义的推导指南生成的函数模板更特化,则选择从构造函数生成的函数模板。因为复制推导候选通常比包装构造函数更特化,所以这条规则意味着通常更喜欢复制而不是包装。

template<class T>
struct A
{
    A(T, int*);     // #1
    A(A<T>&, int*); // #2
 
    enum { value };
};
 
template<class T, int N = T::value>
A(T&&, int*) -> A<T>; //#3
 
A a{1, 0}; // uses #1 to deduce A<int> and initializes with #1
A b{a, 0}; // uses #2 (more specialized than #3) to deduce A<int> and initializes with #2

当较早的决胜因素(包括偏序)无法区分两个候选函数模板时,将应用以下规则

  • 从用户定义的推导指南生成的函数模板优先于从构造函数或构造函数模板隐式生成的函数模板。
  • 复制推导候选优先于从构造函数或构造函数模板隐式生成的所有其他函数模板。
  • 从非模板构造函数隐式生成的函数模板优先于从构造函数模板隐式生成的函数模板。
template<class T>
struct A
{
    using value_type = T;
 
    A(value_type); // #1
    A(const A&);   // #2
    A(T, T, int);  // #3
 
    template<class U>
    A(int, T, U);  // #4
};                 // #5, the copy deduction candidate A(A);
 
A x(1, 2, 3); // uses #3, generated from a non-template constructor
 
template<class T>
A(T) -> A<T>; // #6, less specialized than #5
 
A a(42); // uses #6 to deduce A<int> and #1 to initialize
A b = a; // uses #5 to deduce A<int> and #2 to initialize
 
template<class T>
A(A<T>) -> A<A<T>>; // #7, as specialized as #5
 
A b2 = a; // uses #7 to deduce A<A<int>> and #1 to initialize

如果 cv 未限定的模板参数是类模板参数,则对它的右值引用不是转发引用

template<class T>
struct A
{
    template<class U>
    A(T&&, U&&, int*); // #1: T&& is not a forwarding reference
                       //     U&& is a forwarding reference
 
    A(T&&, int*);      // #2: T&& is not a forwarding reference
};
 
template<class T>
A(T&&, int*) -> A<T>; // #3: T&& is a forwarding reference
 
int i, *ip;
A a{i, 0, ip};  // error, cannot deduce from #1
A a0{0, 0, ip}; // uses #1 to deduce A<int> and #1 to initialize
A a2{i, ip};    // uses #3 to deduce A<int&> and #2 to initialize

当从作为所讨论的类模板特化的类型的单个参数初始化时,通常默认情况下复制推导优先于包装

std::tuple t1{1};  //std::tuple<int>
std::tuple t2{t1}; //std::tuple<int>, not std::tuple<std::tuple<int>>
 
std::vector v1{1, 2};   // std::vector<int>
std::vector v2{v1};     // std::vector<int>, not std::vector<std::vector<int>> (P0702R1)
std::vector v3{v1, v2}; // std::vector<std::vector<int>>

在复制与包装的特殊情况之外,列表初始化中对 initializer-list 构造函数的强烈偏好仍然存在。

std::vector v1{1, 2}; // std::vector<int>
 
std::vector v2(v1.begin(), v1.end()); // std::vector<int>
std::vector v3{v1.begin(), v1.end()}; // std::vector<std::vector<int>::iterator>

在引入类模板参数推导之前,避免显式指定参数的常见方法是使用函数模板

std::tuple p1{1, 1.0};             //std::tuple<int, double>, using deduction
auto p2 = std::make_tuple(1, 1.0); //std::tuple<int, double>, pre-C++17
特性测试宏 标准 特性
__cpp_deduction_guides 201703L (C++17) 类模板的模板参数推导
201907L (C++20) 聚合和别名的 CTAD

[编辑] 缺陷报告

以下行为更改缺陷报告已追溯应用于先前发布的 C++ 标准。

DR 应用于 发布时的行为 正确行为
CWG 2376 C++17 即使声明的变量的类型
与将推导其参数的类模板不同,也会执行 CTAD
在这种情况下不执行
CTAD
CWG 2628 C++20 隐式推导指南不传播约束 传播约束
CWG 2697 C++20 目前尚不清楚是否允许在用户定义的推导指南中使用
缩写的函数模板语法
禁止
CWG 2707 C++20 推导指南不能有尾随 requires 子句 它们可以
CWG 2714 C++17 隐式推导指南未考虑
构造函数的默认参数
考虑它们
P0702R1 C++17 initializer-list 构造函数可以抢占
复制推导候选,导致包装
复制时跳过
initializer-list 阶段