CS106L: Templates

警告
本文最后更新于 2024-02-03,文中内容可能已过时。

CS106L 中关于 Templates 的部分

Templates

template 应该算得上很自然的想法(🤔 又或者是套娃的另一次应用),我认为这就是对函数的进一层抽象,它将函数的逻辑抽象成与类型无关,比如

1
2
3
4
template <typename T>
T min(T a, T b) {
    return (a < b) ? a : b;
}
1
2
3
4
template <typename Type=int>
Type myMin(Type a, Type b) {
    return a < b ? a : b;
}

这里的 typename 没有指明类型,实际上可以写成 class T,这样这个函数就不会接受 int 之类的类型。那个 =int 表示其默认类型(虽然我还没认识到写它的意义)。

可以针对性的再写一个特定类型的模板函数:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
template <> void print_msg<float>() {
  std::cout << "print_msg called with float type!\n";
}

template <bool T> int add3(int a) {
  if (T) {
    return a + 3;
  }

  return a;
}
1
auto minvar = min<int>(1, 2);

隐式存在一个问题在于参数的类型未必能被识别出来(有些类型的定义方式差不多)。不过貌似编译器这时候会报错。

从一个实际的类型推广到一个模板,这个过程被称为 Concept Lifting。对于隐式类型的来说,这种提升可能会导致传入一些不可以工作的类型(比如函数内部使用了 = 赋值,但 stream 是不可以这样做的)

毕竟有了函数指针,其实可以把抽象做的更细一些。比如 Predicate Functions

/img/CS106L/predicate_functions.png

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
template <typename InputIt, typename UniPred>
int count_occurrences(InputIt begin, InputIt end, UniPred pred) {
    int count = 0;
    for (auto iter = begin; iter != end; ++iter) {
        if (pred(*iter)) count++;
    }
    return count;
}
bool isVowel(char c) {
    std::string vowels = "aeiou";
    return vowels.find(c) != std::string::npos;
}

std::string str = "Xadia";
count_occurrences(str.begin(), str.end(), isVowel);

C++20 允许开发者显示指定其 template 类型的要求,具体可以参见文档:Constraints and concepts (since C++20)Requires expression (since C++20)

Lamda function:

1
2
3
auto func = [capture-clause](parameters)->return-value{
    // body
}

C++14 开始,这个 return-value 是可选的。

/img/CS106L/lamdbaintro.png

1
2
3
4
5
6
7
[] // captures nothing
[limit] // captures lower by value
[&limit] // captures lower by reference
[&limit, upper] // captures lower by reference, higher by value
[&, limit] // captures everything except lower by reference
[&] // captures everything by reference
[=] // captures everything by value
1
2
auto isMoreThan = [limit] (int n) { return n > limit; };
isMoreThan(6); //true

有了这个之后,也就不需要像之前那样定义Predicate Functions了,可以直接写 lamdba。

STL 的一些 algorithm 不能用于开发者自定义的类型(比如寻找最小值之类的),这时候需要用到 lambda 函数。

比如对于这样的 vector:

1
std::vector<Student> vecstu{{1, 2, 3.0}, {2, 2, 5.0}};

直接使用 std::minmax_element() 是无法通过编译的

1
auto [min, max] = std::minmax_element(vecstu.begin(), vecstu.end());

额,根据我看到的录像那里,其开发环境是没有在编译前给出预警的。但是我的 vscode 在只给了两个参数的时候:

1
In template: invalid operands to binary expression ('Student' and 'Student') clang(typecheck_invalid_operands)

这时候就可以加一个 lamdba 函数,并传给 minmax_element()

1
2
3
auto compareStudent = [](Student &s1, Student &s2){
    return s1.averge < s2.averge;
};
1
auto [min, max] = std::minmax_element(vecstu.begin(), vecstu.end(), compareStudent);

std::copy 这个函数中,如果传入的 iterator 指向的 container 没有足够的空间,那么就会复制到为初始化的内存中,这时候应该传入一个 iterator adaptor。这种函数可以给 iterator 加点料(比如 back_inserter() 会让返回的 iterator 在赋值不存在的空间时扩展 container)。

引用上一章一开始给出的代码:

1
2
3
4
5
6
int main() {
    std::vector<int> vec(20);
    std::generate(vec.begin(), vec.end(), rand);
    std::sort(vec.begin(), vec.end());
    std::copy(vec.begin(), vec.end(), std::ostream_iterator<int>(cout, "\n"));
}