C++ 模板(一)函数模板最基本的思想

一直以来,对 C++ 的模板和模板元编程,很多人不是视为洪水猛兽,就是当作无用鸡肋。即使是不那么偏激的,也说不定会以为这是只有库开发者才需要去掌握的「独门神功」。然而,当我真正去学习的时候,我发现,或许早期的 C++ 模板还有很多漏洞,但在 C++17 以后,模板已经成为了一套相对自然易学的工具。为了梳理学习脉络,我打算写一个 C++ 模板系列,分享自己的体会。

这个系列不是教材,所以我不会面面俱到地写,尤其是普通的模板语法。具体还是得看 C++ Templates (2nd) 这本书。

模板最基本的思想

世上存在三种多态:Subtyping, Ad Hoc Polymorphism, and Parametric Polymorphism。OOP 常用的继承等就是 Subtyping,函数重载和运算符重载指的是第二个,而模板则是最后一个(听不懂(逃))。

或许,模板在最早期只是想做一个 "Better Macro",但它早已今非昔比,变成了一门完备的语言。如果你之前有过动态弱类型语言(如 Python, JavaScript)的经验,那学习模板时你可能会感到亲切——因为 C++ 中的模板就是一种 Duck Typing 的语言,这种「与普通 C++ 本身格格不入的模式」,使得 C++ 有了能媲美 JavaScript 的灵活性。

首先,我们通过一个简单的例子来说明什么是 Duck Typing。

Duck Typing

"If it looks like a duck and quacks like a duck, it must be a duck."

「如果一个东西长得像一只鸭子,叫得也像一只鸭子,那它就是一只鸭子。」

所谓「鸭子类型」,即不对一个对象的具体类型做出限制(是不是一只真正的鸭子),而是看它如何使用(能不能当作一只鸭子)。请看下面的代码:

template <typename Ptr>
void draw_element(Ptr UI_element) {
  UI_element->draw();
}

其中,template <typename Ptr> 是 C++ 中声明模板的方式。template 关键字指出后面要声明一个模板,<> 之间的内容则是「模板参数」。和普通的参数(在 () 里的参数)一样,模板参数会在模板被使用的时候替代为调用是提供的参数。

// assume ChatWindow and ScrollBar all have a method named `draw()`
void caller(ChatWindow* chat_window, ScrollBar* scroll_bar) {
  // T will be replaced by 'ChatWindow*'
  draw_element<ChatWindow*>(chat_window);
  
  // Template parameters can be omitted if can be auto-deducted
  // equivalent to 'draw_element<ScrollBar*>(scroll_bar)'
  draw_element(scroll_bar)
}

可以看到,模板参数需要在 <> 中提供,普通参数在 () 中提供。如果可以推断出模板参数的类型,就可以不写模板参数。

传入的类型,既可以是一个 ChatWindow*,也可以是一个 ScrollBar*,模板函数 draw_element 并不限制它是什么具体类型,只要它能用在这个语句里:

UI_element->draw();

即使 ChatWindowScrollBar 没有共同基类,即使 ChatWindow::draw() 返回 voidScrollBar::draw() 返回 int,也没有任何问题。

也可以传入一个 std::shared_ptr<ChatWindow>,因为 std::shared_ptr 重载了 -> 运算符。

指明类型约束

有时,我们希望能指定类型约束。这需要使用 concepts

template <typename Ptr>
concept IndirectlyDrawable = requires (Ptr iter) {
  iter->draw(); // 1
};

template <typename T> requires IndirectlyDrawable<T>
void draw_element(T element) {
  element->draw();
}

上面的 concept IndirectlyDrawable 指出一个 Ptr 类型的 ptr 必须可以用在 (1) 处的语句中。

事实上这个例子和上面的例子在使用上没什么差别,因为 draw_element 的函数体已经隐式地声明了对 T 的要求。如果不能满足,这个模板函数就无法调用。

这篇文章就写到这里吧,以后再一点点写更多的笔记。

References

  • David Vandevoorde, Nicolai M.Josuttis, Douglas Gregor, C++ Templates (Second Edition)
posted @ 2020-08-14 17:21  seideun  阅读(359)  评论(0)    收藏  举报