LOADING

加载过慢请开启缓存 浏览器默认开启

C++ Template 阅读笔记(2)

2025/3/21

接上文,继续记录学习 C++ Template 这本书的心得。

变参模板

以下是一个例子:

void print() {}

template <typename T, typename... Types>
void print(T firstArg, Types... args) {
    std::cout << firstArg << std::endl;
    print(args...);
}

上述的代码必须要定义 print() 这个重载函数,否则编译会不通过。因为 Types... args 的参数数量最后会变成 0,这样就找不到对应的函数了,也可以采用如下的版本。

template <typename T>
void print(T arg) {
    std::cout << arg << std::endl;
}

template <typename T, typename... Types>
void print(T firstArg, Types... args) {
    print(firstArg);
    print(args...);
}

当传入的参数为 1 个的时候,会优先匹配第一个函数。

折叠表达式

template <typename... T> 
auto foldSum(T... args) { return (... + args); }

变参下标(Variadic Indices)

template <typename T, typename... Idx>
void printElements(const T &v, Idx... idx) {
  print(v[idx]...);
}
const char *names[]{"Alice", "Bob", "Peter"};
// print "Alice" and "Bob"
printElements(names, 0, 1);

template <size_t... Idx, typename C> void printElements(const C &v) {
  print(v[Idx]...);
}
const char *names[]{"Alice", "Bob", "Peter"};
// print "Bob" and "Peter"
printElements<1, 2>(names);

模板成员变量

template<typename T>
class MyClass {
public:
  static constexpr int size = sizeof(T);
};

template<typename T>
constexpr int Size = MyClass<T>::size;

std::cout << Size<char> << std::endl; // 1
std::cout << Size<double> << std::endl; // 8

各种 type trait,如 std::is_const_v 就是基于此。

从 static_assert 到 std::enable_if_t 再到 concept

static_assert

template <typename T> void foo(T t) {
  static_assert(sizeof(T) > 4,
                "size of given type should be greater than 4 bytes");
  std::cout << t << std::endl;
}
foo(1);

上述代码会报错:

Static assertion failed due to requirement ‘sizeof(int) > 4’: size of given type should be greater than 4 bytesclang(static_assert_requirement_failed)

std::enable_if_t

template <typename T> 
std::enable_if_t<(sizeof(T) > 4)> foo(T t) {
  std::cout << t << std::endl;
}

std::enable_if_t 等价于 std::enable_if::type。其中第一个模板类型是 bool 编译期常量,若为 true,则函数返回第二个模板类型(若没有第二个模板类型,则为 void),否则函数未定义。

上述代码会报未定义错误:

No matching function for call to ‘foo’

concept

template <typename T>
  requires(sizeof(T) > 4)
void foo(T t) {
  std::cout << t << std::endl;
}

类似的报错:

No matching function for call to 'foo'clang(ovl_no_viable_function_in_call)
main.cpp(18, 6): Candidate template ignored: constraints not satisfied [with T = int]
main.cpp(17, 12): Because 'sizeof(int) > 4' (4 > 4) evaluated to false

模板中按值传递 & 按引用传递

引用传递会保留类型的修饰,而值传递则会对类型进行退化。

template<typename T>
void foo(T &t) {
  if constexpr(std::is_array_v<T>) {
    std::cout << "is array" << std::endl;
  }
  if constexpr(std::is_const_v<T>) {
    std::cout << "is const" << std::endl;
  }
}

const char a[] = "Hello, World";
foo(a);
// 输出
// is array
// is const

而将上述代码修改为值传递:

template <typename T> void foo(T t) {
  if constexpr (std::is_array_v<T>) {
    std::cout << "is array" << std::endl;
  }
  if constexpr (std::is_const_v<T>) {
    std::cout << "is const" << std::endl;
  }
}

则不会产生任何输出,因为 const char[13] 被退化为了 char *
既不是 array 也不是 const

下面的函数有时候会产生问题:

template<typename T>
T foo(T t) {
  return t;
}

虽然会对类型进行退化,但是仍然可以通过显式的方式将 T 声明为引用。

int i;
foo<int &>(i);

这样可能会导致垂悬引用(dangling reference),所以此处可以通过三种方式保证返回值是退化的。

// 使用 auto 自动退化
template<typename T>
auto foo(T t) {
  return t;
}

// 使用 std::decay 显式退化
template<typename T>
std::decay_t<T> foo(T t) {
  return t;
}

// 去除引用
template<typename T>
std::remove_reference_t<T> foo(T t) {
  return t;
}

std::is_convertible

std::is_convertible 可以用于限制一个使用万能引用的构造函数的模板类型,例如:

class MyClass {
public:
  template <typename T>
  MyClass(T &&data)
    requires(std::is_convertible_v<T, std::string>)
      : data(data) {}

private:
  std::string data;
};

MyClass a(1); // error

报错信息:

No matching constructor for initialization of 'MyClass'clang(ovl_no_viable_function_in_init)
main.cpp(19, 7): Candidate constructor (the implicit copy constructor) not viable: no known conversion from 'int' to 'const MyClass' for 1st argument
main.cpp(19, 7): Candidate constructor (the implicit move constructor) not viable: no known conversion from 'int' to 'MyClass' for 1st argument
main.cpp(22, 3): Candidate template ignored: constraints not satisfied [with T = int]
main.cpp(23, 14): Because 'std::is_convertible_v<int, std::string>' evaluated to false

std::reference_wrapper

std::string str = "123";
auto r1 = std::ref(str); // std::reference_wrapper<string>
auto r2 = std::cref(str); // std::reference_wrapper<const string>

std::reference_wrapper 相当于在引用外包了一层,这样就可以在保证性能的前提下进行值传递(有些模板采用值传递)。std::reference_wrapper 可以隐式地转换成包裹的引用本身。

std::string &s2 = r1;
const std::string &s3 = r2;