14 模板实参推断

2021-05-29  本文已影响0人  奇点创客

推断的过程

template<typename T>
const T& max(const T& a, const T& b)
{
  return a < b ? b : a;
}
auto g = max(1, 1.0); // T分别推断int和double,推断矛盾
template<typename T>
typename T::E at(T a, int i)
{
  return a[i];
}

void f(int* p)
{
  int x = at(p, 7); // T为int*,T::E构造无效,推断失败
}
template<typename T>
const T& max(const T& a, const T& b);

max("Apple", "Pear"); // 错误:分别是const char[6]和const char[5]

推断上下文(Deduced Context)

template<typename T>
void f1(T*);

template<typename T, int N>
void f2(T(&)[N]);

template<typename T1, typename T2, typename T3>
void f3(T1 (T2::*)(T3*));

class S {
 public:
  void f(double*);
};

void g(int*** ppp)
{
  bool b[42];
  f1(ppp); // T为int**
  f2(b); // T = bool,N = 42
  f3(&S::f); // T1 = void,T2 = S,T3 = double
}
template<int N>
class X {
 public:
  using I = int;
  void f(int) {}
};

template<int N> // X<N>::I是非推断上下文,但X<N>::*p是推断上下文
void f(void (X<N>::*p)(typename X<N>::I));

int main()
{
  f(&X<33>::f); // 正确:由X<N>::*p获得N,再放入X<N>::I,N被推断为33
}
template<typename T>
void f(X<Y<T>, Y<T>>);

void g()
{
  f(X<Y<int>, Y<int>>()); // OK
  f(X<Y<int>, Y<char>>()); // 错误:推断失败
}

特殊的推断情况

template<typename T>
void f(T, T);

void (*pf)(char, char) = &f;
class S {
 public:
  template<typename T>
  operator T&();
};

void f(int(&)[20]);

void g(S s)
{
  f(s); // S要转为int(&)[20],T推断为int[20]
}

初始化列表(Initializer List)的推断

#include <initializer_list>

template<typename T>
void f(T);

int main()
{
  f({1, 2, 3}); // 错误:不能由initializer_list推断T
}
#include <initializer_list>

template<typename T>
void f(std::initializer_list<T>);

int main()
{
  f({2, 3, 5, 7, 9}); // OK:T推断为int
  f({'a', 'e', 'i', 'o', 'u', 42}); // 错误:T推断为char和int
}

参数包的推断

template<typename First, typename... Rest>
void f(First first, Rest... rest);

void g(int i, double j, int* k)
{
  f(i, j, k); // First推断为int,Rest推断为{double, int*}
}
template<typename T, typename U> class X {};

template<typename T, typename... Ts>
void f(const X<T, Ts>&...);

template<typename... Ts1, typename... Ts2>
void g(const X<Ts1, Ts2>&...);

void h(X<int, float> x1, X<int, double> x2, X<double, double> x3)
{
  f(x1, x2); // OK:T推断为int,Ts推断为{float, double}
  g(x1, x2); // OK:Ts1推断为{int, int},Ts2推断为{float, double}
  f(x1, x3); // 错误:T由第一个实参推断为int,由第二个推断为double
  g(x1, x3); // OK:Ts1推断为{int, double},Ts2推断为{float, double}
}
template<typename... Ts> class X { };

template<typename... Ts>
bool f(X<Ts...>, X<Ts...>);

template<typename... Ts1, typename... Ts2>
bool g(X<Ts1...>, X<Ts2...>);

void h(X<short, int> x1, X<unsigned short, unsigned> x2)
{
  f(x1, x1); // OK: Ts推断为{short, int}
  g(x1, x1); // OK: Ts1推断为{short, int},Ts2推断为{short, int}
  f(x1, x2); // 错误:Ts推断为{short, int}和{unsigned short, unsigned}
  g(x1, x2); // OK:Ts1推断为{short, int},Ts2推断为{unsigned short, unsigned}
}

字面值运算符模板(Literal Operator Template)

template<char... cs>
int operator"" _B7()
{
  std::array<char, sizeof...(cs)> chars{cs...};
  for (char c : chars) std::cout << "'" << c << "'";
  return 1; // 随意返回一个值即可
}
int a = 3.14_B7; // 模板实参列表为<'3', '.', '1', '4'>,打印'3' '.' '1' '4'
auto b = 01.3_B7; // OK:推断为<'0', '1', '.', '3'>
auto c = 0xFF00_B7; // OK:推断为<'0', 'x', 'F', 'F', '0', '0'>
auto d = 0815_B7; // 错误:8不是有效的八进制数字
auto e = hello_B7; // 错误:hello不是有效的字面值
auto f = "hello"_B7; // 错误:不匹配模板

引用折叠(Reference Collapsing)

& + & → &
& + && → &
&& + & → &
&& + && → &&
using RCI = const int&; // 底层类型
volatile RCI&& r = 42; // OK: r为const int&
using RRI = int&&; // 底层类型
const RRI&& rr = 42; // OK: rr为int&&

转发引用(Forwarding Reference)

template<typename T>
void f(T&& p);

void g()
{
  int i;
  const int j = 0;
  f(i); // 实参为左值,T推断为int&,参数类型为int&
  f(j); // 实参为左值,T推断为const int&,参数类型为const int&
  f(2); // 实参为右值,T推断为int,参数类型为int&&
}
template<typename T>
void f(T&&)
{
  T x; // 传递左值时,T推断为引用,x未初始化所以出错
}
template<typename T>
void f(T&&)
{
  std::remove_reference_t<T> x; // x一定不会是一个引用
}

完美转发

template<typename T>
void f(T&& x)
{
  g(std::forward<T>(x)); // 把x完美转发给g
}
template<typename... Ts>
void f(Ts&&... args)
{
  g(std::forward<Ts>(args)...); // 把所有args转发给g
}
void g(int*);
void g(...);

template<typename T>
void f (T&& x)
{
  g(std::forward<T>(x));
}

void foo()
{
  g(0); // 调用g(int*)
    f(0); // 调用g(...)
}
g(nullptr); // 调用g(int*)
f(nullptr); // 调用g(int*)
template<typename... Ts>
auto f(Ts&&... args) -> decltype(g(std::forward<Ts>(args)...))
{
  return g(std::forward<Ts>(args)...);
}
template<typename... Ts>
decltype(auto) f(Ts&&... args)
{
  return g(std::forward<Ts>(args)...);
}

SFINAE(Substitution Failure Is Not An Error)

template<typename T, unsigned N>
T* begin(T(&a)[N])
{
  return a;
}

template<typename C>
typename C::iterator begin(C& c)
{
  return c.begin();
}

int main()
{
  std::vector<int> v;
  int a[10];

  ::begin(v); // OK:只匹配第二个,因为第一个替换失败
  ::begin(a); // OK:只匹配第一个,因为第二个替换失败
}
template<typename T>
class Array {
 public:
  using iterator = T*;
};

template<typename T>
void f(Array<T>::iterator first, Array<T>::iterator last);

template<typename T>
void f(T*, T*);

int main()
{
  f<int&>(0, 0); // 错误:在第一个模板中用int&替换T实例化Array<int&>
}
template<typename T>
auto f(T x)
{
  return x->m; // 实例化此定义时,x为int则错误
}

int f(...); // 省略号参数的匹配不好

template<typename T>
auto g(T x) -> decltype(f(x));

int main()
{
  g(42); // 错误:第一个匹配更好,但实例化定义时出错,不会使用SFINAE
}

推断的限制

template<typename T>
class B<T> {};

template<typename T>
class D : B<T> {};

template<typename T>
void f(B<T>*);

void g(D<long> x)
{
  f(&x); // 推断成功
}
template<typename T>
class X {
 public:
  X(T b) : a(b) {}
 private:
  T a;
};

X x(12); // C++17之前错误:不能从构造函数实参推断类模板参数T
template<typename T1, typename T2, typename T3 = T2>
class C {
 public:
  C(T1 x = T1{}, T2 y = T2{}, T3 z = T3{});
};

C c1(1, 3.14, "hi"); // OK:T1 = int,T2 = double,T3 = const char*
C c2(1, 3.14); // OK:T1 = int,T2 = T3 = double
C c3("hi", "guy"); // OK:T1 = T2 = T3 = const char*
C c4; // 错误:T1和T2未定义
C c5("hi"); // 错误:T2未定义
// 注意不能显式指定一部分而推断另一部分
C<string> c10("hi","my", 42); // 错误:只指定了T1,T2未推断
C<> c11(1, 3.14, 2); // 错误:T1和T2都没指定
C<string, string> c12("hi","my"); // OK
template<typename T>
void f(T x = 42)
{}

int main()
{
  f<int>(); // OK: T = int
  f(); // 错误:不能用默认实参推断
}
template<typename T>
void f(T, int) noexcept(nonexistent(T()));

template<typename T>
void f(T, ...); // C-style vararg function

void test(int i)
{
  f(i, i); // 错误:选择第一个模板,但表达式nonexistent(T())无效
}
template<typename T>
void g(T, int) throw(typename T::Nonexistent); // C++11弃用了throw

template<typename T>
void g(T, ...);

void test(int i)
{
  g(i, i); // 错误:选择第一个模板,但类型T::Nonexistent无效
}

显式指定实参

int f(int);
template<typename T> T f(T);

auto x = f(42); // 调用函数
auto y = f<>(42); // 调用模板
void f();

template<typename>
void f();

namespace N {
class C {
  friend int f(); // OK:在N中找不到f,声明一个新的f
  friend int f<>(); // 错误:查找到全局的模板f,返回类型不一致
};
}
template<typename T>
typename T::E f();

template<typename T>
T f();

auto x = f<int*>(); // 选择第二个模板
template<typename... Ts>
void f(Ts... args);

f<double, double, int>(1, 2, 3); // OK:1和2转换为double
g<double, int>(1, 2, 3);  // OK:模板实参是<double, int, int>

auto

template<typename C>
void f(const C& c)
{
  auto it = c.begin();
  while (it != c.end())
  {
    auto& x = *it++;
    … // 对元素进行操作
  }
}

// auto it = c.begin()的推断等价于如下调用模板的推断
template<typename T>
void f2(T it);

f2(c.begin());

// auto& x = *it++的推断等价于如下调用模板的推断
template<typename T>
void f3(T& x);

f3(*it++);
auto&& x = ...;
// 等价于
template<typename T>
void f(T&& x); // auto被T替换

int i;
auto&& rr = 42; // auto推断为int,int&& rr = 42
auto&& lr = i; // auto推断为int&,int& lr = i
template<typename C>
void f(C c)
{
  for (auto&& x: c) ...
}
auto f() { return 42; }
// 也能用尾置返回类型
auto f() -> auto { return 42; }
// 第一个auto声明尾置返回类型
// 第二个auto是用于推断的占位符类型
[] (int x) { return f(x); };
// 等价于
[] (int x) -> auto { return f(x); };
auto f(); // 前置声明
auto f() { return 42; } // OK

int g();
auto g() { return 42; }  // 错误
struct S {
  auto f();
};

auto S::f() { return 42; }
template<typename T, T N>
struct X;

X<int, 42>* x;
template<auto N>
struct X;

X<42>* p; // auto推断为int
X<3.14>* q; // 错误:非类型模板参数不能是浮点数
template<auto N>
struct X {
  using Type = decltype(N);
};
template<typename>
struct PMClassT;

template<typename C, typename M>
struct PMClassT<M C::*> {
  using Type = C;
};

template<typename PM>
using PMClass = typename PMClassT<PM>::Type;

template<auto PMD>
struct CounterHandle {
  PMClass<decltype(PMD)>& c;
  CounterHandle(PMClass<decltype(PMD)>& c): c(c) {}

  void incr()
  {
    ++(c.*PMD);
  }
};

struct S {
  int i;
};

int main()
{
  S s{41};
  CounterHandle<&S::i> h(s);
  h.incr(); // 增加s.i
}
CounterHandle<int S::*, &S::i>
template<auto... VS>
struct Values {};

Values<1, 2, 3> beginning;
Values<1, 'x', nullptr> triplet;
template<auto V1, decltype(V1)... VRest>
struct X {};

decltype

auto x = ...;
auto y1 = x + 1; // y1和x类型不一定相同,如果x是char,y1是int
decltype(x) y2 = x + 1; // y2和x类型一定相同
int&& i = 0;
decltype(i) // int&&(i的实体类型)
decltype((i)) // int&(i的值类型)

decltype(auto)

int i = 42;
const int& r = i;
auto x = r; // int x = r
decltype(auto) y = r; // const int& y = r

std::vector<int> v = { 42 };
auto x2 = v[0]; // int x2 = v[0],x2是一个新对象
decltype(auto) y2 = v[0]; // int& y2 = v[0],y2是一个引用
template<typename C>
class X {
  C c;
  decltype(auto) operator[](std::size_t n)
  {
    return c[n];
  }
};
int i;
decltype(auto) x = i; // int x = i
decltype(auto) r = (i); // int& r = i

int g() { return 1; }

decltype(auto) f()
{
  int r = g();
  return (r); // 返回临时变量的引用,应当避免此情况
}
template<decltype(auto) Val>
class S {};

constexpr int c = 42;
extern int v = 42;

S<c> sc;   // produces S<42>
S<(v)> sv; // produces S<(int&)v>
template<auto N>
struct S {};

template<auto N>
int f(S<N> p);

S<42> x;
int r = f(x);
template<auto V>
int f(decltype(V) p);

int r1 = deduce<42>(42); // OK
int r2 = deduce(42); // 错误:decltype(V)是一个非推断上下文

auto推断的特殊情况

template<typename T>
void f(T);

f({ 1 }); // 错误
f({ 1, 2, 3 }); // 错误

template<typename T>
void g(std::initializer_list<T>);
g({ 1, 2, 3 }); // OK:T推断为int
auto x = { 1, 2 }; // x是initializer_list<int>
auto x  { 1, 2 }; // 错误
auto x { 1 }; // x为int,和auto x(1)效果一样
auto f() { return { 1 }; } // 错误
char c;
auto *p = &c, d = c; // OK
auto e = c, f = c + 1; // 错误:e为char,f为int

auto f(bool b)
{
  if (b)
  {
    return 42.0; // 返回类型推断为double
  }
  else
  {
    return 0; // 错误:推断不一致
  }
}
// 错误例子
auto f(int n)
{
  if (n > 1)
  {
    return n * f(n - 1); // 错误:f(n - 1)类型未知
  }
  else
  {
    return 1;
  }
}

// 正确例子
auto f(int n)
{
  if (n <= 1)
  {
    return 1; // OK:返回类型被推断为int
  }
  else
  {
    return n * f(n - 1); // OK:f(n - 1)为int,所以n * f(n - 1)也为int
  }
}
auto f1() {} // OK:返回类型是void
auto f2() { return; } // OK:返回类型是void
auto* f3() {} // 错误:auto*不能推断为void
template<typename T, typename U>
auto f(T t, U u) -> decltype(t+u)
{
  return t + u;
}

void f(...);

template<typename T, typename U>
auto g(T t, U u) -> decltype(auto)  // 必须实例化t和u来确定返回类型
{
  return t + u;  // 此处的实例化在定义中,不是即时上下文,不适用SFINAE
}

void g(...);

struct X {};

using A = decltype(f(X(), X())); // OK:A为void
using B = decltype(g(X(), X())); // 错误:g<X, X>的实例化非法

结构化绑定(Structured Binding)

struct X { bool valid; int value; };
X g();
const auto&& [b, N] = g(); // 把b和N绑定到g()的结果的成员
// 数组的例子
double pt[3];
auto& [x, y, z] = pt;
x = 3.0; y = 4.0; z = 0.0;

// 另一个例子
auto f() -> int(&)[2];  // f()返回一个int数组的引用

auto [ x, y ] = f(); // auto e = f(), x = e[0], y = e[1]
auto& [ r, s ] = f(); // auto& e = f(), x = e[0], y = e[1]

// std::tuple的例子
std::tuple<bool, int> bi {true, 42};
auto [b, i] = bi; // auto b = get<0>(bi), i = get<1>(bi)
int r = i; // int r = 42
std::tuple_element<i, E>::type& n_i = e.get<i>();
// 如果e推断为引用类型
std::tuple_element<i, E>::type&& n_i = e.get<i>();

// 如果e没有get成员
std::tuple_element<i, E>::type& n_i = get<i>(e);
std::tuple_element<i, E>::type&& n_i = get<i>(e);
#include <utility>

enum M {};

template<>
struct std::tuple_size<M> {
  static unsigned const value = 2; // 将M映射为一对值
};

template<>
struct std::tuple_element<0, M> {
  using type = int; // 第一个值类型为int
};

template<>
struct std::tuple_element<1, M> {
  using type = double; // 第二个值类型为double
};

template<int> auto get(M);
template<> auto get<0>(M) { return 42; }
template<> auto get<1>(M) { return 7.0; }

auto [i, d] = M(); // 相当于int&& i = 42, double&& d = 7.0

泛型lambada(Generic Lambda)

template<typename Iter>
Iter findNegative(Iter first, Iter last)
{
  return std::find_if(first, last,
  [] (typename std::iterator_traits<Iter>::value_type value) {
    return value < 0;
  });
}
template<typename Iter>
Iter findNegative(Iter first, Iter last)
{
  return std::find_if(first, last,
  [] (auto value) {
    return value < 0;
  });
}
[] (int i) {
  return i < 0;
}
// 上述的lambda相当于下面类的一个默认构造对象的简写
class X { // 一个内部类
 public:
  X(); // 只被编译器调用
  bool operator() (int i) const
  {
    return i < 0;
  }
};


foo(..., [] (int i) { return i < 0; });
// 等价于
foo(..., X{}); // 传递一个闭包类型对象
int x, y;
[x,y] (int i) {
  return i > x && i < y;
}
// 将编译为下面的类
class Y {
 public:
  Y(int x, int y) : _x(x), _y(y) {}
  bool operator() (int i) const
  {
    return i > _x && i < _y;
  }
 private:
  int _x, _y;
};
[] (auto i) {
  return i < 0;
}
// 将编译为下面的类
class Z {
 public:
  Z();
  template<typename T>
  auto operator() (T i) const
  {
    return i < 0;
  }
};
#include <iostream>

template<typename F, typename... Ts>
void invoke(F f, Ts... ps)
{
  f(ps...);
}

int main()
{
  ::invoke([](auto x, auto y) {
    std::cout << x + y << '\n';
  },
  21, 21);
}

别名模板(Alias Template)

template<typename T, typename Cont>
class Stack;

template<typename T>
using DequeStack = Stack<T, std::deque<T>>;

template<typename T, typename Cont>
void f1(Stack<T, Cont>);

template<typename T>
void f2(DequeStack<T>);

template<typename T>
void f3(Stack<T, std::deque<T>); // 等价于f2

void test(DequeStack<int> intStack)
{
  f1(intStack); // OK:T推断为int,Cont推断为std::deque<int>
  f2(intStack); // OK:T推断为int
  f3(intStack); // OK:T推断为int
}
template<typename T>
using A = T;

template<>
using A<int> = void; // 错误

Deduction Guide

 explicitopt template-name (parameter-declaration-clause) -> simple-template-id;
template<typename T>
struct X {
  T i;
};

X(const char*) -> X<std::string>;

int main()
{
  X x{ "hello" }; // T推断为std::string
  std::cout << x.i;
}
template<typename T>
class X {
 public:
  X(T b) : a(b) {}
 private:
  T a;
};

template<typename T> X(T) -> X<T>;

int main()
{
  X x{1}; // X<int> x{1}
  X y(1); // X<int> y(1)
  auto z = X{12}; // auto z = X<int>{1}
  X xx(1), yy(2.0); // 错误:X推断为X<int>和X<double>
}
X* p = &x; // 语法错误
template<typename T>
struct X {
  T val;
};

template<typename T> X(T)->X<T>;

int main()
{
  X x1{42}; // OK
  X x2 = {42}; // OK
  X x3(42); // 错误:没有初始化列表,int不能转为X<int>
  X x4 = 42; // 错误:没有初始化列表,int不能转为X<int>
}
template<typename T, typename U>
struct X {
  X(const T&);
  X(T&&);
};

template<typename T> X(const T&) -> X<T, T&>;
template<typename T> explicit X(T&&) -> X<T, T>; // 只有直接初始化能使用

X x1 = 1; // 只使用非explicit声明的deduction guide:X<int, int&> x1 = 1
X x2{2}; // 第二个deduction guide更合适:X<int, int> x2{2}

隐式的Deduction Guide

template<typename T>
class X {
 public:
  X(T b) : a(b) {}
 private:
  T a;
};

// template<typename T> X(T) -> X<T> // 隐式deduction guide
X x{12}; // x类型为X<int>
X y{x}; // y类型为X<int>还是X<X<int>>?
X z(x); // z类型为X<int>还是X<X<int>>?
std::vector v{1, 2, 3};
std::vector v1{v}; // vector<int>
std::vector v2{v, v}; // vector<vector<int>>
template<typename T, typename... Ts>
auto f(T x, Ts... args)
{ // 如果T推断为vector
  std::vector v{ x, args... };  // 参数包是否为空将决定不同的v类型
}
template<typename T>
struct A {
  using Type = T;
};

template<typename T>
class X {
 public:
  using ArgType = typename A<T>::Type;
  X(ArgType b) : a(b) {}
 private:
  T a;
};

// template<typename T> X(typename A<T>::Type) -> X<T>; // 隐式deduction guide
// 该deduction guide无效,因为有限定名称符A<T>::
int main()
{
  X x{1}; // 错误
}

Deduction Guide的其他细微问题

template<typename T>
struct X {
  template<typename U> X(U x);
  template<typename U>
  auto f(U x)
  {
    return X(x); // 根据注入类名规则X是X<T>,根据类模板实参推断X是X<U>
  }
};
template<typename T>
struct X {
  X(const T&);
  X(T&&);
};

// 如果把隐式deduction guide指定出来就将出错
template<typename T> Y(const T&) -> Y<T>; // (1)
template<typename T> Y(T&&) -> Y<T>; // (2)

void f(std::string s)
{
  X x = s; // 预期想通过隐式deduction guide推断T为std::string
  // (1)推断T为std::string,但要求实参转为const std::string
  // (2)推断T为std::string&,是一个更好的匹配,这是预期外的结果
}
template<typename ... Ts>
struct Tuple {
  Tuple(Ts...);
  Tuple(const Tuple<Ts...>&);
};

// 隐式deduction guide
template<typename... Ts> Tuple(Ts...) -> Tuple<Ts...>; // (1)
template<typename... Ts> Tuple(const Tuple<Ts...>&) -> Tuple<Ts...>; // (2)

int main()
{
  auto x = Tuple{1, 2}; // 明显使用(1),x是Tuple<int, int>
  Tuple a = x; // (1)为Tuple<Tuple<int, int>,(2)为Tuple<int, int>,(2)匹配更好
  Tuple b(x); // 和a一样推断为Tuple<int, int>,a和b都由x拷贝构造
  Tuple c{x, x}; // 只能匹配(1),生成Tuple<Tuple<int, int>, Tuple<int, int>>
  Tuple d{x}; // 看起来和c的匹配一样,但会被视为拷贝构造,匹配(2)
  auto e = Tuple{x}; // 和d一样,推断为Tuple<int, int>而非<Tuple<int, int>>
}
template<typename T>
struct X {};

template<typename T>
struct Y {
  Y(const X<T>&);
  Y(X<T>&&);
};

template<typename T> Y(X<T>) -> Y<T>; // 虽然不对应构造函数但没有关系
// 对于一个X<T>类型的值x将选用可推断类型Y<T>
// 随后初始化并对Y<T>的构造函数进行重载解析
// 根据x是左值还是右值决定调用的构造函数
上一篇下一篇

猜你喜欢

热点阅读