diff --git a/README.md b/README.md index dd8a5b12d..aaafc06e4 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ --- -![English Coverage](https://img.shields.io/badge/en_coverage-100%25-green.svg) 592/594 docs translated +![English Coverage](https://img.shields.io/badge/en_coverage-100%25-green.svg) 602/604 docs translated ## 这是什么项目 diff --git a/code/examples/vol4/vol1-basics-cpp11-14/comparable_mixin.cpp b/code/examples/vol4/vol1-basics-cpp11-14/comparable_mixin.cpp new file mode 100644 index 000000000..7237d05b0 --- /dev/null +++ b/code/examples/vol4/vol1-basics-cpp11-14/comparable_mixin.cpp @@ -0,0 +1,30 @@ +// Comparable:CRTP mixin,只实现 < 和 ==,自动补齐 > <= >= != +// 对应文章:documents/vol4-advanced/vol1-basics-cpp11-14/09-crtp.md +// 编译:g++ -Wall -Wextra -std=c++20 comparable_mixin.cpp -o comparable && ./comparable +#include + +template struct Comparable { + friend bool operator>(const Derived& a, const Derived& b) { return b < a; } + friend bool operator<=(const Derived& a, const Derived& b) { return !(b < a); } + friend bool operator>=(const Derived& a, const Derived& b) { return !(a < b); } + friend bool operator!=(const Derived& a, const Derived& b) { return !(a == b); } +}; + +struct Point : Comparable { + int x, y; + Point(int x_, int y_) : x(x_), y(y_) {} + // 只实现 < 和 ==,其余四个由 Comparable 自动补齐 + friend bool operator<(const Point& a, const Point& b) { return a.x < b.x; } + friend bool operator==(const Point& a, const Point& b) { return a.x == b.x; } +}; + +int main() { + Point p1{1, 2}, p2{3, 4}; + std::cout << std::boolalpha; + std::cout << "p1 < p2: " << (p1 < p2) << "\n"; + std::cout << "p1 > p2: " << (p1 > p2) << "\n"; + std::cout << "p1 <= p2: " << (p1 <= p2) << "\n"; + std::cout << "p1 >= p2: " << (p1 >= p2) << "\n"; + std::cout << "p1 != p2: " << (p1 != p2) << "\n"; + return 0; +} diff --git a/code/examples/vol4/vol1-basics-cpp11-14/crtp_static_polymorphism.cpp b/code/examples/vol4/vol1-basics-cpp11-14/crtp_static_polymorphism.cpp new file mode 100644 index 000000000..c879237c9 --- /dev/null +++ b/code/examples/vol4/vol1-basics-cpp11-14/crtp_static_polymorphism.cpp @@ -0,0 +1,47 @@ +// CRTP 静态多态:派生类把自己传给基类,编译期绑定,无 vtable 无运行时分派 +// 对应文章:documents/vol4-advanced/vol1-basics-cpp11-14/09-crtp.md +// 编译运行:g++ -Wall -Wextra -std=c++20 crtp_static_polymorphism.cpp -o crtp && ./crtp +// 看零开销汇编:g++ -O2 -std=c++20 -DCRTP_BENCH -S crtp_static_polymorphism.cpp -o crtp.s +// use_crtp() 会被完全内联成 mov $0x2a,%eax; ret(对比虚函数版的 vtable 解引用) +#include + +template struct Shape { + const char* name() { return static_cast(this)->name_impl(); } + double area() { return static_cast(this)->area_impl(); } +}; + +struct Circle : Shape { + double r; + explicit Circle(double r_) : r(r_) {} + const char* name_impl() { return "Circle"; } + double area_impl() { return 3.14159 * r * r; } +}; + +struct Square : Shape { + double side; + explicit Square(double s) : side(s) {} + const char* name_impl() { return "Square"; } + double area_impl() { return side * side; } +}; + +#ifdef CRTP_BENCH +// 汇编基准:use_crtp 在 -O2 下被完全内联,直接返回常量 42,零函数调用 +template struct BenchBase { + int compute() { return static_cast(this)->compute_impl(); } +}; +struct BenchConcrete : BenchBase { + int compute_impl() { return 42; } +}; +int use_crtp() { + BenchConcrete c; + return c.compute(); +} +#endif + +int main() { + Circle c{2.0}; + Square s{3.0}; + std::cout << c.name() << " area = " << c.area() << "\n"; // Circle 走 Circle::area_impl + std::cout << s.name() << " area = " << s.area() << "\n"; // Square 走 Square::area_impl + return 0; +} diff --git a/code/examples/vol4/vol1-basics-cpp11-14/fixed_vector.cpp b/code/examples/vol4/vol1-basics-cpp11-14/fixed_vector.cpp new file mode 100644 index 000000000..02e166358 --- /dev/null +++ b/code/examples/vol4/vol1-basics-cpp11-14/fixed_vector.cpp @@ -0,0 +1,46 @@ +// fixed_vector:编译期定长、连续存储、零动态分配的 vector +// 对应文章:documents/vol4-advanced/vol1-basics-cpp11-14/10-fixed-vector.md +// 编译:g++ -Wall -Wextra -std=c++20 fixed_vector.cpp -o fixed_vector && ./fixed_vector +#include +#include +#include +#include + +template class FixedVector { + std::array data_{}; + std::size_t size_ = 0; + + public: + static constexpr std::size_t capacity_v = N; + + constexpr void push_back(const T& value) { + if (size_ >= N) + throw std::out_of_range("FixedVector full"); + data_[size_++] = value; + } + constexpr T& operator[](std::size_t i) { return data_[i]; } + constexpr const T& operator[](std::size_t i) const { return data_[i]; } + constexpr std::size_t size() const { return size_; } + + // 裸指针当迭代器:元素连续存储,T* 天生满足随机访问迭代器要求 + constexpr T* begin() { return data_.data(); } + constexpr T* end() { return data_.data() + size_; } + constexpr const T* begin() const { return data_.data(); } + constexpr const T* end() const { return data_.data() + size_; } +}; + +int main() { + FixedVector v; + for (int i = 1; i <= 5; ++i) + v.push_back(i * 10); + + std::cout << "size = " << v.size() << " capacity = " << decltype(v)::capacity_v << "\n"; + std::cout << "elements: "; + for (auto x : v) + std::cout << x << " "; + std::cout << "\n"; + std::cout << "v[2] = " << v[2] << "\n"; + std::cout << "sizeof(FixedVector) = " << sizeof(FixedVector) << "\n"; + std::cout << "sizeof(int*) = " << sizeof(int*) << " (动态 vector 至少含 3 个指针 + 堆分配)\n"; + return 0; +} diff --git a/code/examples/vol4/vol1-basics-cpp11-14/type_traits_from_scratch.cpp b/code/examples/vol4/vol1-basics-cpp11-14/type_traits_from_scratch.cpp new file mode 100644 index 000000000..a126c5c96 --- /dev/null +++ b/code/examples/vol4/vol1-basics-cpp11-14/type_traits_from_scratch.cpp @@ -0,0 +1,50 @@ +// 手写 type traits:主模板 + 偏特化,看穿 的实现原理 +// 对应文章:documents/vol4-advanced/vol1-basics-cpp11-14/04-specialization-partial.md +// 编译:g++ -Wall -Wextra -std=c++20 type_traits_from_scratch.cpp -o type_traits && ./type_traits +#include + +// is_pointer:主模板 false,指针偏特化 true +template struct is_pointer { + static constexpr bool value = false; +}; +template struct is_pointer { + static constexpr bool value = true; +}; + +// is_const:主模板 false,const T 偏特化 true(注意 const T* 不算,得 T 本身是 const) +template struct is_const { + static constexpr bool value = false; +}; +template struct is_const { + static constexpr bool value = true; +}; + +// is_reference:主模板 false,T& / T&& 偏特化 true +template struct is_reference { + static constexpr bool value = false; +}; +template struct is_reference { + static constexpr bool value = true; +}; +template struct is_reference { + static constexpr bool value = true; +}; + +// C++14 变量模板简写(和标准库 _v 后缀同一思路) +template constexpr bool is_pointer_v = is_pointer::value; + +int main() { + std::cout << std::boolalpha; + std::cout << "is_pointer = " << is_pointer::value << "\n"; + std::cout << "is_pointer = " << is_pointer::value << "\n"; + std::cout << "is_pointer = " << is_pointer::value << "\n"; + std::cout << "is_const = " << is_const::value << "\n"; + std::cout << "is_const = " << is_const::value << "\n"; + std::cout << "is_const = " << is_const::value + << " (指针自身非 const,指向 const)\n"; + std::cout << "is_const = " << is_const::value << " (指针自身 const)\n"; + std::cout << "is_reference = " << is_reference::value << "\n"; + std::cout << "is_reference = " << is_reference::value << "\n"; + std::cout << "is_pointer_v = " << is_pointer_v << " (_v 简写)\n"; + return 0; +} diff --git a/documents/en/vol4-advanced/vol1-basics-cpp11-14/01-templates-introduction.md b/documents/en/vol4-advanced/vol1-basics-cpp11-14/01-templates-introduction.md new file mode 100644 index 000000000..a49dc1420 --- /dev/null +++ b/documents/en/vol4-advanced/vol1-basics-cpp11-14/01-templates-introduction.md @@ -0,0 +1,212 @@ +--- +chapter: 12 +cpp_standard: +- 11 +- 14 +- 17 +- 20 +description: 'Strip templates back to what they really are: a code recipe with placeholders. + How they differ from macros and virtual dispatch, and the four kinds of template + entities in C++ (functions, classes, variables, aliases).' +difficulty: intermediate +order: 1 +platform: host +prerequisites: +- Volume 1 · Function Templates +reading_time_minutes: 11 +related: +- 'Function Templates, In Depth: Compilation Model and extern template' +- 'Class Templates: Members, Dependent Names, Lazy Instantiation' +tags: +- host +- cpp-modern +- intermediate +- 模板 +- 泛型 +title: 'Templates, From Scratch: A Code Recipe with Placeholders' +--- +# Templates, From Scratch: A Code Recipe with Placeholders + +We already wrote function templates back in Volume 1. We know that `template T max_value(T a, T b)` lets the compiler stamp out a version for `int`, for `double`, for `std::string`. This volume stops at "how to use them" and asks different questions. What is a template, really? How does it pull off "write once, fit any type"? And what does that mechanism cost? Get the under-the-hood details straight, and reading the STL source, reading template-heavy industrial code like Chromium, or writing a library of your own stops feeling intimidating. + +## What a Template Actually Is: A Code Recipe with Placeholders + +I like to think of a template as a **code recipe**. The template itself is not code; it is a description of how the code should be written. `T` is a placeholder in the recipe. It says "leave this blank for now, fill it in when the recipe is actually used." + +```cpp +template +T max_value(T a, T b) { + return (a > b) ? a : b; +} +``` + +These lines produce no machine code on their own. The compiler just records the recipe. What actually produces machine code is **instantiation**. When we write `max_value(3, 5)`, the compiler sees that `3` and `5` are both `int`, copies the recipe with `T` replaced by `int`, and gets a real `int max_value(int, int)` function. That copy is what gets compiled. + +```cpp +int x = max_value(3, 5); // T=int, compiler stamps out an int version +double y = max_value(1.0, 2.0); // T=double, stamps out a double version +``` + +These two calls produce two completely independent functions, each compiled separately. The effect is the same as if you had written two overloads by hand. + +Here is a counterintuitive point worth pausing on. Many people assume a template picks a version at runtime based on type. It does not. It generates a separate version at compile time for every type you use. So a template call has **no runtime overhead**: you are calling an ordinary function, with no vtable dispatch. The cost comes later, and we will get to it. + +::: warning A placeholder is not a macro substitution +Some people read templates as "a fancier macro substitution," and that is half right. A macro (`#define`) is pure text replacement at the preprocessing stage. It ignores types, ignores scope, and trips you up the moment you stop paying attention. Template substitution happens during compilation. The compiler knows `T` is a type, and it runs type checking, overload resolution, and name lookup on it. When we get to two-phase lookup later, you will see that this mechanism is far more precise than a macro. Treating templates as "a macro with type checking" is fine as a first impression, but do not stop there. +::: + +## Why Not a Macro, and Why Not a Virtual Function + +"Same logic, different types" is a need C++ answers in several ways. Let us line up the three most often compared. + +Macros. `#define max(a, b) ((a) > (b) ? (a) : (b))` works, but it is pure text replacement at preprocessing. Arguments get evaluated twice, types are ignored, there is no scope, and the debugger never sees it. Back in Volume 1 we mentioned the `max` macro from `` on Windows, the one that sends your blood pressure through the roof. Anything a macro can do, a template does better. + +Virtual dispatch. Write `max` as a virtual function, and the right implementation is picked at runtime based on the actual type of the object. This forces every participating type into one inheritance hierarchy, and every call goes through a vtable lookup, which costs runtime. Worse, builtin types like `int` and `double` cannot enter your hierarchy at all. + +Templates. The compiler generates a separate copy for every type used, and the call goes to an ordinary, inlineable function. No inheritance hierarchy required. Builtin types and user-defined types are treated the same. No runtime dispatch. + +None of these replaces the others. Virtual dispatch fits "one interface, implementation decided at runtime" cases like plugin systems or GUI event dispatch. Templates fit "one piece of logic, type known at compile time" cases like containers and algorithms. As for macros, mostly avoid them when you can. + +## C++ Has Four Kinds of Template Entities + +A lot of people think templates come in two flavors, function templates and class templates. The C++ standard actually defines four kinds of entities a template can produce. cppreference puts it plainly: a template is a C++ entity that defines a family of entities. + +A function template defines a family of functions; `std::max` and `std::sort` are examples. A class template defines a family of classes; `std::vector` and `std::map` are examples. Both have been around since C++98, and these are the two you know best. There are two newer ones. A variable template (since C++14) defines a family of variables or static members. An alias template (since C++11) defines a family of type aliases. + +Variable templates answer a plain need: attach a constant to "each type." Before C++14, people simulated this with a static member of a class template. + +```cpp +// The old way, before C++14: simulate "a parameterized constant" with a class template static member +template +struct pi_trait { + static constexpr T value = T(3.1415926535897932385L); +}; +double r = pi_trait::value * 2.0; +``` + +C++14 gave us variable templates, and you can write it as a single parameterized variable, much cleaner. + +```cpp +template +constexpr T pi = T(3.1415926535897932385L); // variable template + +double r = pi * 2.0; // reads like an ordinary constant, just with a +``` + +Part of the reason `std::numeric_limits::max()` is a function rather than a variable is that variable templates did not exist when it was born. `std::tuple_size` and `std::extent` later grew matching `_v` variable-template versions (things like `std::extent_v`, introduced in C++17), precisely so you could stop writing `::value`. The feature-test macro on cppreference is `__cpp_variable_templates = 201304L`, tied to C++14. + +Alias templates answer the need to give a family of types a short name. + +```cpp +// Before alias templates, naming vector meant borrowing a nested using inside a class template +template +struct vec_alias { using type = std::vector; }; +typename vec_alias::type v; // typename, then ::type, noisy + +// C++11 alias template, one line +template +using vec = std::vector; +vec v; // clean +``` + +This volume has a whole piece on alias templates later. For now, just hold this impression: **in C++, it is not only functions and classes that can be parameterized**. + +## Three Kinds of Template Parameters + +The "placeholder" in a template comes in three forms. + +A type parameter, written `typename T` or `class T`, stands in for a type. This is the most common. A non-type parameter, written like `template `, stands in for a compile-time **value**: an integer, a pointer, a reference, and since C++20 a floating-point value or a class type that meets certain conditions. A template template parameter stands in for a template itself. + +```cpp +template // T is a type parameter, N is a non-type parameter +struct array { + T data[N]; +}; + +template