C++模板参数推导

C++模板参数推导

  • 参数包(Parameter pack)
    • 模板参数包
    • 函数参数包
  • 从函数调用中推导参数类型
    • 多个参数
    • 推导P'[N]
    • 参数包是最后一个P
    • P是函数类型,函数类型的指针或成员函数类型的指针
  • 非推导上下文

为了实例化函数模板,必须知道每个模板参数,但不是必须指定每个模板参数。如果可能,编译器将从函数参数中推断出缺少的模板参数。当尝试调用函数、获取函数模板的地址,以及在其他一些上下文中时,会发生这种情况:

类型转换

template<typename ToType, typename FromType> ToType convert(FromType f);
 
void g(double d) 
{
    int i = convert<int>(d);    // calls convert(double)
    char c = convert<char>(d);  // calls convert(double)
    int(*ptr)(float) = convert; // instantiates convert(float) 
                                // and stores its address in ptr
}

参数包(Parameter pack)

模板参数包

模板参数包是接受零个或多个模板参数(非类型、类型或模板)的模板参数。

函数参数包

函数参数包是接受零个或多个函数参数的函数参数。
具有至少一个参数包的模板称为可变参数模板。

从函数调用中推导参数类型

模板实参推导尝试确定模板实参(类型模板参数 Ti 的类型、模板模板参数 TTi 的模板和非类型模板参数 Ii 的值),可以将其代入每个参数 P 以生成推导出的类型 A,其中与参数 A 的类型相同,经过以下列出的调整。

多个参数

如果有多个参数,则分别推导每个 P/A 对,然后,将推导的模板参数组合起来。如果任何 P/A 对的推导失败或不明确,或者不同的对产生不同的推导模板参数,或者任何模板参数既没有推导也没有明确指定,则编译失败。

如果从 P 中删除引用和 cv 限定符给出 std::initializer_list,并且 A 是一个花括号初始化列表,则对初始化列表的每个元素执行推导,以 P’ 作为参数和列表元素A’ 作为论据:

从函数调用中推导参数类型

template<class T> void f(std::initializer_list<T>);
f({1, 2, 3});  // P = std::initializer_list, A = {1, 2, 3}
               // P'1 = T, A'1 = 1: deduced T = int
               // P'2 = T, A'2 = 2: deduced T = int
               // P'3 = T, A'3 = 3: deduced T = int
               // OK: deduced T = int
f({1, "abc"}); // P = std::initializer_list, A = {1, "abc"}
               // P'1 = T, A'1 = 1: deduced T = int
               // P'2 = T, A'2 = "abc": deduced T = const char*
               // error: deduction fails, T is ambiguous

推导P’[N]

如果从 P 中删除引用和 cv 限定符,得到 P’[N],并且 A 是非空的大括号初始化列表,则按上述执行推导。除非 N 是非类型模板参数,否则推导从初始化列表的长度:

如何得到 P'[N]的例子

template<class T, int N> void h(T const(&)[N]);
h({1, 2, 3}); // deduced T = int, deduced N = 3
 
template<class T> void j(T const(&)[3]);
j({42}); // deduced T = int, array bound is not a parameter, not considered
 
struct Aggr { int i; int j; };
template<int N> void k(Aggr const(&)[N]);
k({1, 2, 3});       // error: deduction fails, no conversion from int to Aggr
k({{1}, {2}, {3}}); // OK: deduced N = 3
 
template<int M, int N> void m(int const(&)[M][N]);
m({{1, 2}, {3, 4}}); // deduced M = 2, deduced N = 2
 
template<class T, int N> void n(T const(&)[N], T);
n({{1}, {2}, {3}}, Aggr()); // deduced T = Aggr, deduced N = 3

参数包是最后一个P

如果参数包作为最后一个 P 出现,则类型 P 与调用的每个剩余参数的类型 A 匹配。每个匹配都推导出包扩展中下一个位置的模板参数:

参数包是最后一个P的例子

template<class... Types> void f(Types&...);
 
void h(int x, float& y)
{
    const int z = x;
    f(x, y, z); // P = Types&..., A1 = x: deduced first member of Types... = int
                // P = Types&..., A2 = y: deduced second member of Types... = float
                // P = Types&..., A3 = z: deduced third member of Types... = const int
                // calls f
}

P是函数类型,函数类型的指针或成员函数类型的指针

如果P是函数类型,指向函数类型的指针,或指向成员函数类型的指针,并且,如果 A 是一组不包含函数模板的重载函数,则每次重载都会尝试推导模板参数。如果只有一个成功,则使用成功的推导。如果没有或不止一个成功,则模板参数是非推导上下文:

P是函数类型的例子

template<class T> int f(T(*p)(T));
int g(int);
int g(char);
f(g); // P = T(*)(T), A = overload set
      // P = T(*)(T), A1 = int(int): deduced T = int
      // P = T(*)(T), A2 = int(char): fails to deduce T
      // only one overload works, deduction succeeds

在推导开始之前,对 P 和 A 进行以下调整:

  1. 如果 P 不是引用类型,
    a) 如果 A 是数组类型,则将 A 替换指针类型,将数组到指针转换得到该指针类型;
    b) 否则,如果 A 是函数类型,则将 A 替换指针类型,将函数转换成指针,得到该类型;
    c) 否则,如果 A 是 cv 限定类型,则忽略顶级 cv 限定符进行推导:

例子

template<class T> void f(T);
int a[3];
f(a); // P = T, A = int[3], adjusted to int*: deduced T = int*
void b(int);
f(b); // P = T, A = void(int), adjusted to void(*)(int): deduced T = void(*)(int)
const int c = 13;
f(c); // P = T, A = const int, adjusted to int: deduced T = int
  1. 如果 P 是 cv 限定类型,则忽略顶级 cv 限定符进行推导。
  2. 如果 P 是引用类型,则引用类型用于推导。
  3. 如果 P 是对 非cv限定类型模板参数的右值引用,并且对应的函数调用参数是左值,则使用对 A 的类型左值引用代替 A 进行推导:

例子

template<class T>
int f(T&&);       // P is an rvalue reference to cv-unqualified T (forwarding reference)
template<class T>
int g(const T&&); // P is an rvalue reference to cv-qualified T (not special)
 
int main()
{
    int i;
    int n1 = f(i); // argument is lvalue: calls f(int&) (special case)
    int n2 = f(0); // argument is not lvalue: calls f(int&&)
 
//  int n3 = g(i); // error: deduces to g(const int&&), which
                   // cannot bind an rvalue reference to an lvalue
}

在这些转换之后,推导过程如下所述,它尝试找到这样的模板参数,这些参数将使推导的 A与转换后的 A 相同,即上面列出的调整后的 A。

如果通常从 P 和 A 中推导失败,则另外考虑以下备选方案:

  1. 如果 P 是引用类型,则推导的 A(即,引用所引用的类型)可以比转换后的 A 更具 cv 限定性:

例子

template<typename T> void f(const T& t);
bool a = false;
f(a); // P = const T&, adjusted to const T, A = bool:
      // deduced T = bool, deduced A = const bool
      // deduced A is more cv-qualified than A
  1. 转换后的 A 可以是另一个指针,或指向成员类型的指针,可以通过限定转换或函数指针转换(C++17 起)将其这些指针转换为推导的 A:

例子

template<typename T> void f(const T*);
int* p;
f(p); // P = const T*, A = int*:
      // deduced T = int, deduced A = const int*
      // qualification conversion applies (from int* to const int*)
  1. 如果 P 是一个类,并且 P 的形式为 simple-template-id,则转换后的 A 可以是推导出的 A 的派生类。同样,如果 P 是一个指向 simple-template-id 形式的类的指针,转换后的 A 可以是指向由推导的 A 指向的派生类的指针:

例子

template<class T> struct B { };
template<class T> struct D : public B<T> { };
template<class T> void f(B<T>&) { }
 
void f()
{
    D<int> d;
    f(d); // P = B&, adjusted to P = B (a simple-template-id), A = D:
          // deduced T = int, deduced A = B
          // A is derived from deduced A
}

非推导上下文

在以下情况下,用于构成 P 的类型、模板和非类型值不参与模板参数推导,而是使用在其他地方推导,或显式指定的模板参数。如果模板参数仅在非推导上下文中使用,且未明确指定,则模板参数推导失败。

  1. 使用限定 ID 指定的类型的嵌套名称说明符(范围解析运算符 :: 左侧的所有内容):

例子

// the identity template, often used to exclude specific arguments from deduction
// (available as std::type_identity as of C++20)
template<typename T> struct identity { typedef T type; };
template<typename T> void bad(std::vector<T> x, T value = 1);
template<typename T> void good(std::vector<T> x, typename identity<T>::type value = 1);

std::vector<std::complex<double>> x;

bad(x, 1.2);  // P1 = std::vector, A1 = std::vector>
              // P1/A1: deduced T = std::complex
              // P2 = T, A2 = double
              // P2/A2: deduced T = double
              // error: deduction fails, T is ambiguous

good(x, 1.2); // P1 = std::vector, A1 = std::vector>
              // P1/A1: deduced T = std::complex
              // P2 = identity::type, A2 = double
              // P2/A2: uses T deduced by P1/A1 because T is to the left of :: in P2
              // OK: T = std::complex
  1. decltype-specifier 的表达式:

例子

template<typename T> void f(decltype(*std::declval<T>()) arg);
int n;
f<int*>(n); // P = decltype(*declval()), A = int: T is in non-deduced context
  1. 非类型模板参数,或绑定的数组,其中子表达式引用模板参数:

例子

template<std::size_t N> void f(std::array<int, 2 * N> a);
std::array<int, 10> a;
f(a); // P = std::array, A = std::array:
      // 2 * N is non-deduced context, N cannot be deduced
      // note: f(std::array a) would be able to deduce N
  1. 在函数形参的形参类型中使用的模板形参,该形参有一个默认实参,该实参在进行实参推导的调用中使用:

例子

template<typename T, typename F>
void f(const std::vector<T>& v, const F& comp = std::less<T>());
std::vector<std::string> v(3);
f(v); // P1 = const std::vector&, A1 = std::vector lvalue
      // P1/A1 deduced T = std::string
      // P2 = const F&, A2 = std::less rvalue
      // P2 is non-deduced context for F (template parameter) used in the
      // parameter type (const F&) of the function parameter comp,
      // that has a default argument that is being used in the call f(v)
  1. 参数 P,其 A 是一个函数或一组重载;如果A是函数,则多个函数匹配 P ,或没有函数匹配 P ;如果A是一组重载,则重载集包含一个或多个函数模板:

例子

template<typename T> void out(const T& value) { std::cout << value; }
out("123");     // P = const T&, A = const char[4] lvalue: deduced T = char[4]
out(std::endl); // P = const T&, A = function template: T is in non-deduced context
  1. 参数 P,其 A 是花括号初始化列表,但 P 不是 std::initializer_list,对一个的引用(可能是 cv 限定的),或(自 C++17 起)对数组的引用:

例子

template<class T> void g1(std::vector<T>);
template<class T> void g2(std::vector<T>, T x);
g1({1, 2, 3});     // P = std::vector, A = {1, 2, 3}: T is in non-deduced context
                   // error: T is not explicitly specified or deduced from another P/A
g2({1, 2, 3}, 10); // P1 = std::vector, A1 = {1, 2, 3}: T is in non-deduced context
                   // P2 = T, A2 = int: deduced T = int
  1. 参数 P 是一个参数包,而且它不会出现在参数列表的末尾:

例子

template<class... Ts, class T> void f1(T n, Ts... args);
template<class... Ts, class T> void f2(Ts... args, T n);
f1(1, 2, 3, 4); // P1 = T, A1 = 1: deduced T = int
                // P2 = Ts..., A2 = 2, A3 = 3, A4 = 4: deduced Ts = [int, int, int]
f2(1, 2, 3, 4); // P1 = Ts...: Ts is non-deduced context
  1. 出现在参数 P 中的模板参数列表,其中包括一个不在模板参数列表末尾的包扩展:

例子

template<int...> struct T { };
 
template<int... Ts1, int N, int... Ts2>
void good(const T<N, Ts1...>& arg1, const T<N, Ts2...>&);
 
template<int... Ts1, int N, int... Ts2>
void bad(const T<Ts1..., N>& arg1, const T<Ts2..., N>&);
 
T<1, 2> t1;
T<1, -1, 0> t2;
good(t1, t2); // P1 = const T&, A1 = T<1, 2>:
              // deduced N = 1, deduced Ts1 = [2]
              // P2 = const T&, A2 = T<1, -1, 0>:
              // deduced N = 1, deduced Ts2 = [-1, 0]
bad(t1, t2);  // P1 = const T&, A1 = T<1, 2>:
              //  is non-deduced context
              // P2 = const T&, A2 = T<1, -1, 0>:
              //  is non-deduced context
  1. 对于数组类型的 P(但不引用数组,或指向数组的指针),主数组绑定:

例子

template<int i> void f1(int a[10][i]);
template<int i> void f2(int a[i][20]);    // P = int[i][20], array type
template<int i> void f3(int (&a)[i][20]); // P = int(&)[i][20], reference to array
 
void g()
{
    int a[10][20];
    f1(a);     // OK: deduced i = 20
    f1<20>(a); // OK
    f2(a);     // error: i is non-deduced context
    f2<10>(a); // OK
    f3(a);     // OK: deduced i = 10
    f3<10>(a); // OK
}

你可能感兴趣的:(程序设计方法,#,C++,程序语言C++系列,c++,开发语言,后端)