[{"content":"Let\u0026rsquo;s do some language lawyer questions. I came across this on Reddit a while ago. The author gave it a very chuunibyou (edgy) name (, I went through them in my free time and ported them over here.\nOriginal repository: https://github.com/0xd34df00d/you-dont-know-cpp Assigning to references Does this work? If it doesn\u0026rsquo;t, why and what\u0026rsquo;s the easiest fix?\n1 2 3 4 5 6 7 8 9 10 11 constexpr decltype(auto) Get() { static int longLiving = 0; auto\u0026amp; ref = longLiving; return ref; } void DoFoo() { Get() = 42; } What about this one? If this one doesn\u0026rsquo;t, why and what\u0026rsquo;s the easiest fix?\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 struct Foo { int a; }; template\u0026lt;int Idx, typename T\u0026gt; constexpr decltype(auto) Get(T\u0026amp; f) { auto\u0026amp; [...fields] = f; // C++26 structured binding introducing a pack return fields... [Idx]; } void DoFoo() { Foo f; Get\u0026lt;0\u0026gt;(f) = 42; } The first one is correct, and the second one is wrong. Why?\nAccording to the decltype rules , we can deduce the following:\nSyntax decltype Deduction Result Value Category Deduced Return Type Result return fields...[Idx]; Rule 1: decltype(entity) id-expression int (by value) Yields a prvalue, error return (fields...[Idx]); Rule 2: decltype(expression) lvalue int\u0026amp; (by reference) Yields an lvalue, valid Without parentheses, fields...[Idx] is an id-expression. It triggers the decltype(entity) rule. For structured bindings, the compiler directly extracts the underlying type of the variable it binds to. Here, the underlying type is int. Therefore, decltype(auto) deduces the function\u0026rsquo;s return type as int. The function returns a prvalue, and you cannot assign a value to a prvalue.\nWhen we add parentheses, the parentheses forcefully change its grammatical property, making it an lvalue expression. At this point, the compiler triggers the decltype(expression) rule. Because the expression is an lvalue, the standard dictates that decltype must deduce it as a reference type, int\u0026amp;. Therefore, the function returns a reference to the original data, making the assignment operation completely valid.\nI found a very detailed blog post demystifying this: C++ value categories and decltype demystified: https://www.scs.stanford.edu/ ~dm/blog/decltype.html\nDefaulted equality Does this work?\n1 2 3 4 5 6 7 8 9 10 11 12 13 #include \u0026lt;compare\u0026gt; // note no operator== struct Foo { int a; std::strong_ordering operator\u0026lt;=\u0026gt;(const Foo\u0026amp;) const = default; }; bool testFoo(Foo f1, Foo f2) { return f1 == f2; } Does this work?\n1 2 3 4 5 6 7 8 9 10 11 12 struct Bar { int a; std::strong_ordering operator\u0026lt;=\u0026gt;(const Bar\u0026amp;) const; }; std::strong_ordering Bar::operator\u0026lt;=\u0026gt;(const Bar\u0026amp;) const = default; bool testBar(Bar b1, Bar b2) { return b1 == b2; } \u0026lt;=\u0026gt; is a new feature introduced in C++20. Its core idea is that instead of returning a bool upon comparison, it returns an ordering relationship. This is a very convenient feature that makes overloading operators a lot easier. It is an abstraction that I really like.\nHere is an example: https://godbolt.org/z/34EjovMca In section 3.2 of this clause , it states:\nIf the member-specification does not explicitly declare any member or friend named operator==, an == operator function is declared implicitly for each three-way comparison operator function defined as defaulted in the member-specification , with the same access and function-definition and in the same class scope as the respective three-way comparison operator function, except that the return type is replaced with bool and the declarator-id is replaced with operator==\nIn the first example, \u0026lt;=\u0026gt; is declared as default, so == is simultaneously declared. However, in the second example, since we didn\u0026rsquo;t provide a default implementation for \u0026lt;=\u0026gt; inside the member specification, the compiler won\u0026rsquo;t declare the == function for us. Even if we supplement the implementation of \u0026lt;=\u0026gt; later on, == still relies on us implementing it manually.\nSpecialization fun You have this in your header:\n1 2 3 4 5 6 7 8 9 10 11 12 template\u0026lt;typename\u0026gt; constexpr auto IsSimpleContainer = [] { struct Undefined {}; return Undefined {}; } (); template\u0026lt;typename T\u0026gt; constexpr bool IsSimpleContainer\u0026lt;std::vector\u0026lt;T\u0026gt;\u0026gt; = true; // vectors of bools are very special! template\u0026lt;\u0026gt; constexpr bool IsSimpleContainer\u0026lt;std::vector\u0026lt;bool\u0026gt;\u0026gt; = false; template\u0026lt;typename T\u0026gt; constexpr bool IsSimpleContainer\u0026lt;std::deque\u0026lt;T\u0026gt;\u0026gt; = true; How can this bite you?\nIt\u0026rsquo;s alright if only one TU includes this header. But if more than one does, the linker might complain on a CWG 2387-conforming implementation: a fully specialized variable template (the one for std::vector\u0026lt;bool\u0026gt;) is a variable definition, so all the usual variable linkage rules apply.\nThe fix is to add inline to that line only:\n1 2 template\u0026lt;\u0026gt; inline constexpr bool IsSimpleContainer\u0026lt;std::vector\u0026lt;bool\u0026gt;\u0026gt; = false; Also, constexpr doesn\u0026rsquo;t help: unlike constexpr functions, constexpr variables are not implicitly inline.\nBonus points for …\n… immediately thinking \u0026ldquo;unless they are static class data members, of course!\u0026rdquo; when reading the previous sentence.\nrequires-constrained return types 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 template\u0026lt;typename... Args\u0026gt; struct Dummy { int value; void foo() requires(sizeof... (Args) == 0) { } std::optional\u0026lt;Args...\u0026gt; foo() requires(sizeof... (Args) == 1) { return {}; } std::optional\u0026lt;std::tuple\u0026lt;Args...\u0026gt;\u0026gt; foo() requires(sizeof... (Args) \u0026gt; 1) { return {}; } }; int main() { Dummy\u0026lt;\u0026gt; d { 42 }; } Dummy\u0026lt;\u0026gt; does not typecheck. Do you expect it to not typecheck? Why it does not typecheck and how to fix it?\nSolution constraint\nNo, you are not allowed to hide it under auto + deduced type. For the actual type in the actual use case that prompted writing this, the return type then needs to be written in every branch, and it\u0026rsquo;s annoying, and also reduces discoverability of the API.\nIt seems like this would perfectly trigger SFINAE, but note that in std::optional\u0026lt;Args...\u0026gt; foo, if Args... is empty, this becomes a hard error, exactly as the compiler outputs: Too few template arguments for class template 'optional'.\nHow do we fix it?\nWe can use a base class DummyBase to wrap it, and then specialize Dummy based on the pack size, like this: https://godbolt.org/z/vdPsojqv3 We can also use a helper type to wrap it, ensuring that the type inside optional is never empty, like this:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 template\u0026lt;typename... Ts\u0026gt; struct OptionalReturn { using type = void; }; template\u0026lt;typename T\u0026gt; struct OptionalReturn\u0026lt;T\u0026gt; { using type = std::optional\u0026lt;T\u0026gt;; }; template\u0026lt;typename... Ts\u0026gt; requires (sizeof...(Ts) \u0026gt; 1) struct OptionalReturn\u0026lt;Ts...\u0026gt; { using type = std::optional\u0026lt;std::tuple\u0026lt;Ts...\u0026gt;\u0026gt;; }; template\u0026lt;typename... Args\u0026gt; struct Dummy { int value; // Now even if Args is empty, OptionalReturn\u0026lt;Args...\u0026gt;::type is just a type definition // It won\u0026#39;t trigger the hard error from std::optional expanding incorrectly typename OptionalReturn\u0026lt;Args...\u0026gt;::type foo(); }; Bonus question\nSome usual approaches don\u0026rsquo;t work:\nMaking foo itself a template with a default template parameter, like 1 2 3 template\u0026lt;typename... MyArgs = Args...\u0026gt; requires(sizeof...(MyArgs) == 1) std::optional\u0026lt;MyArgs...\u0026gt; foo() is not well-formed since packs can\u0026rsquo;t have default values. Using something like template\u0026lt;typename T = std::tuple_element_t\u0026lt;0, std::tuple\u0026lt;Args...\u0026gt;\u0026gt; and then having std::optional\u0026lt;T\u0026gt; in the \u0026ldquo;unary\u0026rdquo; foo() case: std::tuple_element_t hard-errors on out-of-bounds index instead of merely being SFINAEd away. A C++26 variation of that with pack indexing with template\u0026lt;typename T = Args...[0]\u0026gt;: out-of-bounds in pack indexing is also somehow a hard error instead of being SFINAEd away. Given this, what can you say about orthogonality and well-thought-ness of C++?\nhhh, hard to say.\nIs this valid? 1 2 3 4 5 6 7 8 9 struct Foo { struct Nested { bool field = true; }; void doSmth(const Nested\u0026amp; = Nested{}); }; Answer: see this bugzilla entry .\nTo allow us to use variables defined later in class member functions, the compiler won\u0026rsquo;t process two things immediately until the entire class definition is finished. First is the initial values of class member variables, and second is the default arguments of functions (requiring constructors).\nIn this code, since doSmth is still inside the class and our Nested doesn\u0026rsquo;t have a constructor yet, once we write const Nested\u0026amp; = Nested{}, it means we require Nested to have a constructor. What is this? A circular dependency. All options exhausted, sad.\nUnderstanding the principle, it seems easy to fix: we just need to manually add a constructor to Nested. However, the following code will still have errors:\n1 2 3 4 5 6 7 8 9 10 struct Foo { struct Nested { bool field = true; Nested() = default; }; void doSmth(const Nested\u0026amp; = Nested{}); }; Because declaring a constructor as default is not the same as explicitly stating that our Nested class has a constructor like Nested() {};. default does not mean the Nested class will definitely have a constructor; it depends on the class\u0026rsquo;s implementation.\nIt is worth mentioning that MSVC will compile this successfully, hahahaha.\nSome covariance Is this valid?\n1 2 3 4 5 6 7 8 9 struct Base { virtual Base* getFoo() { return nullptr; } }; struct Derived : Base { Derived* getFoo() override { return nullptr; } }; Sure: this is covariance in action.\nWhat about this?\n1 2 3 4 5 6 7 8 9 struct Base { virtual const Base* getFoo() { return nullptr; } }; struct Derived : Base { Base* getFoo() override { return nullptr; } }; Yep, also good: in some sense, Base* is a subtype of const Base*. And, of course, Derived* would\u0026rsquo;ve worked too.\nNow, this is surely valid too, right?\n1 2 3 4 5 6 7 8 9 struct Base { virtual const int* getFoo() { return 0; } }; struct Derived : Base { int* getFoo() override { return 0; } }; Nope: non-class types play by different rules, because otherwise the language would\u0026rsquo;ve been too consistent (see https://eel.is/c++draft/class.virtual #8).\nSo the question is, how do we implement this correctly?\nAn obvious idea is to wrap our int, but this isn\u0026rsquo;t generic enough and is too cumbersome.\nUh, if we ignore the runtime environment, we can solve this problem very well using CRTP:\n1 2 3 4 5 6 7 8 template \u0026lt;typename Derived, typename T\u0026gt; struct Base { T *getFoo() { return static_cast\u0026lt;Derived *\u0026gt;(this)-\u0026gt;getFooImpl(); } }; struct Derived : Base\u0026lt;Derived, int\u0026gt; { int *getFooImpl() { return \u0026amp;data; } int data; }; Are there other methods? We can also handle it like this:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 struct Base { virtual const int *getFoo() const { return getFooImpl(); } protected: virtual const int *getFooImpl() const { return nullptr; } }; struct Derived : Base { using Base::getFoo; int *getFoo() { return const_cast\u0026lt;int *\u0026gt;(getFooImpl()); } protected: const int *getFooImpl() const override { return nullptr; } }; Writing it this way is obviously very ugly\u0026hellip;\nconstexpr string literals Does this compile?\n1 2 3 4 5 constexpr auto f() { return \u0026#34;f\u0026#34;; } constexpr auto g() { return \u0026#34;g\u0026#34;; } static_assert(f() == f()); static_assert(f() != g()); Here the answer is easy: it\u0026rsquo;s an open question, discussed in CWG #2765 .\nYou might get Static assertion expression is not an integral constant expression because it is comparing string addresses.\nIf you want to compare them, please do not use auto.\nWhen is this function safe or unsafe to use? 1 2 template\u0026lt;auto V\u0026gt; const auto\u0026amp; foo() { return V; } To be honest, I\u0026rsquo;m hallucinating a bit seeing this:\n1 2 auto\u0026lt;auto auto\u0026gt; auto auto\u0026amp; auto() { auto auto; } It\u0026rsquo;s safe for class types and unsafe for, say, ints. For some reason the standard threats them differently, so\n1 2 3 4 const auto\u0026amp; v1 = foo\u0026lt;42\u0026gt;(); // bad! dangling reference struct S { int val; }; const auto\u0026amp; v2 = foo\u0026lt;S { 42 }\u0026gt;(); // fine! Finding the corresponding clauses in the standard is left as an exercise for the reader.\nMaps of non-copyable, non-movable types Suppose you have a type that\u0026rsquo;s not copyable nor movable, like\n1 2 3 4 5 struct ThreadedResource { std::unique_ptr\u0026lt;Resource\u0026gt; handle; std::mutex mutex; // mutex isn\u0026#39;t move-constructible nor move-assignable nor copyable }; Suppose you need a hashmap mapping from int to ThreadedResource. One approach is to wrap ThreadedResource with shared_ptr, like std::unordered_map\u0026lt;int, std::shared_ptr\u0026lt;ThreadedResource\u0026gt;\u0026gt;. A null pointer indicates no mapping here.\nThis is annoying (?) because it incurs additional memory overhead and indirection, leading to a performance drop.\nCan you do better?\nOne possible answer is to use std::optional, which can express \u0026ldquo;no value\u0026rdquo; more clearly.\nBecause the object cannot be moved, we have to use piecewise construction (piecewise_construct):\n1 2 3 4 5 6 7 auto handle = ...; map.emplace(std::piecewise_construct, // Construct the key and value piecewise // forward as tuple: pack the arguments required by the constructor into a tuple std::forward_as_tuple(locale), // key std::forward_as_tuple( std::in_place, // Construct in-place std::move(handle)) Please note that the order of the fields in ThreadedResource is mutex after handle.\nTherefore, there is no need to pass an initializer to mutex, and everything will work fine.\nIncrementing enums Is this valid?\n1 2 3 4 5 6 enum E { A, B }; E\u0026amp; operator++(E\u0026amp; e) { // some implementation } Although we cannot increment an enum directly, we can overload the ++ operator for the enum.\nFor example:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 #include \u0026lt;iostream\u0026gt; #include \u0026lt;cassert\u0026gt; enum Status { ready, // 0 running, // 1 stopped // 2 }; // Overload prefix ++ Status\u0026amp; operator++(Status\u0026amp; s) { if (s == stopped) { s = ready; } else { s = static_cast\u0026lt;Status\u0026gt;(static_cast\u0026lt;int\u0026gt;(s) + 1); } return s; } int main() { Status myStatus = ready; ++myStatus; // Now myStatus becomes running assert(myStatus == running); return 0; } operator new Is this valid?\n1 2 3 4 5 6 7 8 9 struct Bar { int n; void* operator new(size_t sz) { return ::operator new(sz + n); } }; How about this?\n1 2 3 4 5 6 7 struct Bar { virtual void* operator new(size_t sz) { return ::operator new(sz); } }; Obviously incorrect. The new operator is implicitly static, and at this point, n has not been fully created yet. Since the object doesn\u0026rsquo;t exist, where does the memory allocation come from? For virtual functions, similarly, there is no vtbl at this point.\nHow are these two functions different? 1 2 3 4 5 template\u0026lt;typename T\u0026gt; T mkT1() { return {}; } template\u0026lt;typename T\u0026gt; T mkT2() { return T {}; } mkT1 is copy-list-initialization , and mkT2 is direct-list-initialization.\nThe direct difference between them lies in the handling of explicit constructors. The mkT1 function does not allow calling constructors marked as explicit.\nMoreover, before C++17, T{} would create a temporary object, which caused things like std::mutex to be unusable here.\nIs this code valid? 1 2 3 4 5 6 7 8 9 10 11 12 13 struct Foo { int a; Foo() = delete; Foo(int) = delete; }; int main() { Foo foo1 {}; Foo foo2 { 10 }; } This also involves C++ initialization. Before C++20, a class was considered an aggregate if:\nIt has no user-provided constructors It has no private or protected non-static data members It has no base classes and no virtual functions Here, Foo() = delete is user-declared, but not user-provided. So it is treated as an aggregate. In aggregate initialization, the compiler bypasses the constructor and directly assigns values to members without needing a constructor.\nThis leads to the above seemingly unreasonable code being able to compile under the C++17 standard.\nBtw, C++20 modified the definition of aggregates, and the above code cannot compile in C++20 and above.\n(^=\u0026hellip;=^) What does this code do, and on what features of C++17 does it rely?\n1 2 3 4 5 6 template\u0026lt;typename F, typename... Ts\u0026gt; void foo(F f, Ts... ts) { int _; (_ = ... = (f(ts), 0)); } This title makes me hallucinate our reflection [:O_o:]\nThis code is very hard to read. Actually, there\u0026rsquo;s a lot of such unreadable code in C++ templates\u0026hellip;\nIt relies on variadic templates, fold expressions, and the evaluation order of the assignment operator. Simply put, if we call foo(func, 1, 2, 3), it will sequentially call func(3), func(2), func(1).\nLet\u0026rsquo;s explain it in detail.\nSuppose we call foo(f, t1, t2). (_ = ... = (f(ts), 0)) is a binary left fold. Its structure is (Init op ... op Pack).\nAfter expansion, it looks like this: ((_ = (f(t1), 0)) = (f(t2), 0)). According to the C++17 standard, in the expression A = B, B is evaluated before A. So it executes t2 first and then t1.\nWhat\u0026rsquo;s wrong with this code? When we secretly overload operator, or operator=, there might be problems.\nOf course, there is one biggest problem: the readability is just too, too poor\u0026hellip;\nConceptual concepts Assume the following declarations:\n1 2 3 4 5 6 7 8 9 10 template \u0026lt;typename T\u0026gt; concept Trivial = std::is_trivial_v\u0026lt;T\u0026gt;; template \u0026lt;typename T, typename U\u0026gt; requires Trivial\u0026lt;T\u0026gt; void f(T t, U u) { std::cout \u0026lt;\u0026lt; 1; } template \u0026lt;typename T, typename U\u0026gt; requires Trivial\u0026lt;T\u0026gt; \u0026amp;\u0026amp; Trivial\u0026lt;U\u0026gt; void f(T t, U u) { std::cout \u0026lt;\u0026lt; 2; } Is f(1, 2) valid? If yes, what would it print?\nWhat if Trivial\u0026lt;T\u0026gt; \u0026amp;\u0026amp; Trivial\u0026lt;U\u0026gt; is replaced by Trivial\u0026lt;T\u0026gt; \u0026amp;\u0026amp; Trivial\u0026lt;T\u0026gt; in the second definition?\nWhat about Trivial\u0026lt;T\u0026gt; || Trivial\u0026lt;U\u0026gt;?\nWhat if the definition of Trivial gets \u0026ldquo;inlined\u0026rdquo;, replacing all Trivial\u0026lt;T\u0026gt;s with sd::is_trivial_v\u0026lt;T\u0026gt;?\nThe answer is 2,\n1 2 3 4 5 Call to \u0026#39;f\u0026#39; is ambiguousclang(ovl_ambiguous_call) foo.cpp(10, 6): Candidate function [with T = int, U = int] foo.cpp(16, 6): Candidate function [with T = int, U = int] For 1, it\u0026rsquo;s ambiguous.\nFun with fun templates What does bar1 print?\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 template\u0026lt;typename T\u0026gt; int foo(T) { return 1; } template\u0026lt;\u0026gt; int foo(int*) { return 2; } template\u0026lt;typename T\u0026gt; int foo(T*) { return 3; } void bar1() { int test; std::cout \u0026lt;\u0026lt; foo(\u0026amp;test) \u0026lt;\u0026lt; foo\u0026lt;int\u0026gt;(\u0026amp;test) \u0026lt;\u0026lt; foo\u0026lt;int*\u0026gt;(\u0026amp;test) \u0026lt;\u0026lt; \u0026#39;\\n\u0026#39;; } What if we reorder the definitions, as in bar2?\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 template\u0026lt;typename T\u0026gt; int foo(T) { return 1; } template\u0026lt;typename T\u0026gt; int foo(T*) { return 3; } template\u0026lt;\u0026gt; int foo(int*) { return 2; } void bar2() { int test; std::cout \u0026lt;\u0026lt; foo(\u0026amp;test) \u0026lt;\u0026lt; foo\u0026lt;int\u0026gt;(\u0026amp;test) \u0026lt;\u0026lt; foo\u0026lt;int*\u0026gt;(\u0026amp;test) \u0026lt;\u0026lt; \u0026#39;\\n\u0026#39;; } Can we still specialize the first template after we\u0026rsquo;ve introduced the second one?\nYep:\n1 2 template\u0026lt;\u0026gt; int foo\u0026lt;int*\u0026gt;(int*) { return 2; } Let\u0026rsquo;s talk about this together with the one above. These are notes I took back when I learned template metaprogramming:\nDuring the specialization of class templates, the compiler will first convert the template into a function template and use function template overloading to determine priority.\nFunction template partial ordering rules:\nIf template B can handle all situations that template A can handle, but template A may not be able to handle situations that template B can handle, then template A is more specialized than B.\nThe article roughly means: the compiler fabricates a type U, substitutes type U into template A to generate a concrete function signature. Then it uses this function signature to try and match template B. If it matches, it means A is more specialized than B. Doing this in reverse allows comparing the specialization degree of A and B.\n1 2 template \u0026lt;typename T\u0026gt; void foo(T); // #1 template \u0026lt;typename T\u0026gt; void foo(T *); // #2 If we want to compare the specialization degree of #1 and #2, first, try substituting #2 into #1. We use a template argument U (e.g., int) to substitute into #2. That is, template \u0026lt;typename T = U\u0026gt; void foo(U *); (foo(int *)). Then try to substitute U* into #1, which is template \u0026lt;typename T\u0026gt; void foo(U *) (you can imagine foo(int *) trying to match #1). At this time, T in #1 can be deduced as U*.\nThen, we try substituting #1 into #2. Similarly, substitute a template argument U into #1: template \u0026lt;typename T = U\u0026gt; void foo(U); and try to match it with #2, resulting in T* = U -\u0026gt; Failed.\nIn conclusion: the specialization degree of #2 is higher than #1.\nFunction templates can be both overloaded and fully specialized. Every overload of a function template is a primary template. During instantiation, overload resolution is performed first, followed by specialization matching. This means that during the overload resolution phase, only primary templates are considered, not their full specializations. After a primary template is selected, specialization matching occurs. Such rules lead to this: if the position of the template specialization is different, the ultimately matched template might also be different. Therefore, we shouldn\u0026rsquo;t use full specialization of function templates, but rather function overloading.\nApplying it here:\nThe analysis below is by AIGC.\n1 2 3 template\u0026lt;typename T\u0026gt; int foo(T) { return 1; } // #1 (Primary template) template\u0026lt;\u0026gt; int foo(int*) { return 2; } // Specialization of #1 (since only #1 is visible here) template\u0026lt;typename T\u0026gt; int foo(T*) { return 3; } // #2 (Another primary template) foo(\u0026amp;test): Primary templates #1 (T=int*) and #2 (T=int) are both in the candidate list. According to the partial ordering rules, #2 is more specialized than #1 ($T*$ is better than $T$). Select #2. Since #2 has no specialized version here, it returns 3. foo\u0026lt;int\u0026gt;(\u0026amp;test): Explicitly specify T=int. Only #2 matches (foo(int*)). Returns 3. foo\u0026lt;int*\u0026gt;(\u0026amp;test): Explicitly specify T=int*. #1 becomes foo(int*), which matches. #2 becomes foo(int**), which doesn\u0026rsquo;t match. Select #1. Check #1\u0026rsquo;s specializations, find foo(int*), and return 2. Result: 332\n1 2 3 template\u0026lt;typename T\u0026gt; int foo(T) { return 1; } // #1 template\u0026lt;typename T\u0026gt; int foo(T*) { return 3; } // #2 template\u0026lt;\u0026gt; int foo(int*) { return 2; } // Specialization of #2 (since #2 is more specialized than #1) Note: Here, the full specialization template\u0026lt;\u0026gt; int foo(int*) will associate with the currently best-matching primary template, which is #2.\nfoo(\u0026amp;test): Select primary template #2. Check its specializations, find foo(int*). Returns 2. foo\u0026lt;int\u0026gt;(\u0026amp;test): Select primary template #2. Check its specializations, find foo(int*). Returns 2. foo\u0026lt;int*\u0026gt;(\u0026amp;test): #1 matches, #2 doesn\u0026rsquo;t match. Select #1. #1 has no specialization here. Returns 1. Result: 221\nI C memset Assume an instance of a struct is memseted to zeroes. What would be the value of the padding?\nFurther assume a field of that structure is updated. What would be the value of the padding after that field? After other fields?\nThey are all unspecified.\nTo understand this more intuitively, let\u0026rsquo;s look at its memory model.\nSuppose we have such a struct:\n1 2 3 4 5 6 7 struct Sample { char a; // 1 byte // Padding: 3 bytes int b; // 4 bytes char c; // 1 byte // Padding: 3 bytes }; After we finish memset:\nAs shown in the figure, whether before or after executing s.a = 'x';, the value of the padding bits is unreliable. Why? Shiranai (I have no idea). But since the standard dictates it this way, don\u0026rsquo;t rely on this behavior (would people really not rely on it?).\nAnyway, on my computer:\nIs this code valid? 1 2 char arr[5] = { 0 }; auto pastEnd = arr + 10; What about this one?\n1 2 char arr[5] = { 0 }; auto pastEnd = arr + 5; The first one is invalid, the second one is valid. But be careful not to do this:\n1 2 3 char arr[5]; char *pastEnd = arr + 5; char value = *pastEnd; // UB Which lines are UB, if any? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 #include \u0026lt;iostream\u0026gt; struct Foo1 { int a; }; struct Foo2 { int a; Foo2() = default; }; struct Foo3 { int a; Foo3(); }; Foo3::Foo3() = default; int main() { Foo1 foo11, foo12 {}; Foo2 foo21, foo22 {}; Foo3 foo31, foo32 {}; std::cout \u0026lt;\u0026lt; foo11.a \u0026lt;\u0026lt; std::endl; std::cout \u0026lt;\u0026lt; foo12.a \u0026lt;\u0026lt; std::endl; std::cout \u0026lt;\u0026lt; foo21.a \u0026lt;\u0026lt; std::endl; std::cout \u0026lt;\u0026lt; foo22.a \u0026lt;\u0026lt; std::endl; std::cout \u0026lt;\u0026lt; foo31.a \u0026lt;\u0026lt; std::endl; std::cout \u0026lt;\u0026lt; foo32.a \u0026lt;\u0026lt; std::endl; } I found a slightly older GCC version https://godbolt.org/z/bTE77GEs3 You can observe that 11, 21, 31, 32 are all UB.\n11, 12, 13 are UB, which is obvious. But why is 32? (Note: referring to foo32)\nIt involves user-provided constructors https://eel.is/c++draft/dcl.fct.def.default .\nAccording to the C++ standard, providing a constructor outside the class is considered a user-provided constructor. For user-provided constructors, the compiler calls that constructor directly and no longer performs additional zero-initialization.\nTherefore, if you implement a constructor outside the class, it\u0026rsquo;s best to manually initialize all members.\nIs this code valid? 1 2 3 4 5 6 7 struct X { int a, b; }; X *make_x() { X *p = (X*)malloc(sizeof(struct X)); p-\u0026gt;a = 1; p-\u0026gt;b = 2; return p; } Depends on the C++ version, and whether it is C++ to begin with.\nUp until C++17, neither an x object nor an int subobjects are created, and this code is UB.\nStarting with C++20, an x object and its int subobjects are implicitly created, and this code is valid.\nIt always has been valid C code, though.\nI thought this was correct (after all, I\u0026rsquo;ve only written code like this in C).\nBefore C++20, malloc did not create objects. When accessing p-\u0026gt;a, a real X object does not exist at that memory address. Objects must be explicitly created using new.\nIf we want to write it correctly:\n1 2 3 4 5 6 7 8 9 10 11 12 13 X *make_x() { // Allocate raw memory void *mem = std::malloc(sizeof(X)); if (!mem) return nullptr; // Use placement new to start the lifetime of X at that address. X *p = new (mem) X; p-\u0026gt;a = 1; p-\u0026gt;b = 2; return p; } Is using this function dangerous? 1 2 3 4 auto foo1() { return \u0026#34;Gotta love C++\u0026#34;; } What about this one?\n1 2 3 4 5 auto foo2() { const char *str = \u0026#34;Gotta love C++\u0026#34;; return str; } This one?\n1 2 3 4 5 auto foo3() { const char str[] = \u0026#34;Gotta love C++\u0026#34;; return str; } Nope, nope, yep.\nWhy? What\u0026rsquo;s the crucial difference between these functions? Is there any difference in their types?\nAccessing any element of the \u0026ldquo;array\u0026rdquo; returned by foo1 and foo2 is fine. Try doing that to foo3 and you\u0026rsquo;ll get an UB, since you\u0026rsquo;ll be using an object whose lifetime has ended!\nfoo1 and foo2 return a pointer to a string that is, roughly speaking, allocated and stored somewhere in the executable at compile time.\nThe pointer returned by foo3 references the local array str which is initialized by copying that same string. This array is local to foo3 and its lifetime ends once the function has returned, hence the UB.\nWhile modern compilers output a warning, what\u0026rsquo;s a reliable and somewhat general way to check functions like this?\nMark all these functions constexpr and try using them in a constant evaluated context, like static_assert:\n1 2 3 static_assert(foo1()[0] == \u0026#39;G\u0026#39;); static_assert(foo2()[0] == \u0026#39;G\u0026#39;); static_assert(foo3()[0] == \u0026#39;G\u0026#39;); Say, clang outputs:\n1 2 3 4 5 6 7 8 9 error: non-constant condition for static assertion 22 | static_assert(foo3()[0] == \u0026#39;G\u0026#39;); | ~~~~~~~~~~^~~~~~ error: accessing \u0026#39;str\u0026#39; outside its lifetime 22 | static_assert(foo3()[0] == \u0026#39;G\u0026#39;); | ~~~~~~~~^ note: declared here 16 | const char str[] = \u0026#34;Gotta love C++\u0026#34;; | ^~~ What does this print? 1 2 3 4 5 6 7 8 9 struct Evil { auto begin() { return std::counted_iterator(\u0026#34;Library\u0026#34;, 7); } friend auto begin(Evil\u0026amp;) { return std::counted_iterator(\u0026#34;Core\u0026#34;, 4); } friend auto end(Evil\u0026amp;) { return std::default_sentinel; } }; Evil rg; for (char c : rg) { putchar(c); } std::ranges::for_each(rg, [](char c) { putchar(c); }); borrowed from Arthur O\u0026rsquo;Dwyer\u0026rsquo;s blog, where he also considers this in more detail\nThe output is CoreLibrary.\nThis is probably a very cliché topic, but it is demonstrated rather subtly here. Usually, std::swap is used to explain ADL and CPO.\nCPO and tag invoke are relatively important features in modern C++, and ranges heavily uses CPOs in its implementation.\nAre these functions different? 1 2 3 4 5 6 7 8 9 int f() { int x = 0; return *(\u0026amp;x - 1 + 1); } int g() { int x = 0; return *(\u0026amp;x + 1 - 1); } Would f() be a good function? (\nIn fact, f is UB, while g is fine.\nborrowed from Daniil Zhuravlev\u0026rsquo;s blog, where he explains what language in the C++ Standard makes it UB and shows a proof that function f is fishy\nConclusion I must be crazy to have actually finished writing this. If you ask me: \u0026ldquo;If I learn all of this, can I become a C++ guru?\u0026rdquo;, I think not. After all, obsessing over obscure language features will most likely just make you an obsessive fanatic.\nI\u0026rsquo;m tired :) Take a break.\nP.S. The English text above was translated by a Large Language Model without manual proofreading. Please excuse any unnatural phrasing or slight losses in the original emotional nuance.\n","date":"2026-05-31T12:10:07+08:00","permalink":"https://anfsity.com/en/p/you-dont-know-cpp-and-neither-i-do/","title":"You Dont Know Cpp and Neither I Do"},{"content":"Recently, I watched some blogs and videos that resonated with me so deeply I couldn\u0026rsquo;t hold it in anymore, so I have to get it off my chest.\nIn my short eighteen years of life, there are a few nodes that I consider very important to me.\nI come from a rural background, not exactly a privileged one, and my parents are both working-class laborers. But looking back at the past and how I\u0026rsquo;ve grown into who I am today, it truly makes me sigh with emotion.\nIn middle school, I met some good teachers. It wasn\u0026rsquo;t a prestigious school, but those teachers left an extremely deep impression on me.\nI remember taking classes back then. No matter what we were learning, it was always with great passion (except for Chinese, perhaps; the Chinese teacher\u0026rsquo;s class was too boring, and I was always drowsy). The teachers weren\u0026rsquo;t necessarily brilliant at teaching, but in my memory, they were always very gentle.\nAt that time, I loved going to the teachers\u0026rsquo; office, because sometimes they would offer a little snack to eat. For a kid with absolutely no pocket money like me, this was highly tempting. But you couldn\u0026rsquo;t go to the office without a reason, so usually, I used asking questions as an excuse. Over time, I got quite familiar with the teachers.\nOf course, being called in to recite texts, make up for missing homework, and getting scolded were also unavoidable.\nOne thing I remember deeply is that when we had to do physics experiments with experiment kits (I scavenged mine from the \u0026ldquo;holy relics\u0026rdquo; left behind by senior students, haha), purely out of curiosity (the battery power was too low, and the motor wouldn\u0026rsquo;t spin fast enough), I connected an electric motor made of copper wire directly to the classroom\u0026rsquo;s AC power supply. Luckily, I had good fortune. I just remember a burst of electrical sparks erupting from the plastic board with a massive popping sound, and then the whole classroom\u0026rsquo;s circuit breaker tripped, plunging us into darkness. It was during a break, and the whole class was completely baffled, hahaha.\nEven now, I still admire my own hands-on ability, but that kind of thing was way too dangerous. It was a classic case of the \u0026ldquo;fearless newborn calf.\u0026rdquo;\nThen there was a physics class. It was almost time for dismissal, and we were waiting to go eat. I was so bored that I was fidgeting and playing around in my seat; I remember tossing an eraser. The atmosphere in the class was already restless before lunch, and my actions were like adding fuel to the fire.\nThe physics teacher was quite young, and I used to hang around him a lot. Perhaps out of a sense of arrogant familiarity, I didn\u0026rsquo;t listen to him at the time, and the teacher lost his temper—a rare occurrence (probably the only time).\nI remember crying and going to find him to apologize. The teacher didn\u0026rsquo;t say much in the end. I don\u0026rsquo;t quite remember what happened afterward, but he probably didn\u0026rsquo;t scold me further.\nAfter that incident, I rarely misbehaved in class again.\nOur biology teacher changed once. I remember the previous one was an old grandpa, and I really liked his classes, but unfortunately, I made him angry once too. Geography was previously taught by our vice-principal, and later changed to a young female teacher.\nHowever, the math and history teachers remained the same for three years. The history teacher was also very nice, which can be seen from the fact that I was the history class representative.\nMy math teacher was my homeroom teacher. I never made her angry, and she was quite fond of me.\nI received a lot of encouragement from her, and my interest in math definitely has her contribution to thank.\nWhen I graduated from middle school, I gave her a hairpin. It wasn\u0026rsquo;t anything valuable; my mom and I picked it out at a department store. I was too shy and awkward to give it to her at first, so I waited until everyone else had left, ran up to her, and spoke with a voice as quiet as a mosquito. But I remember she was very happy at the time, her eyes full of bright surprise.\nThree years later, after graduating from high school, I went back to visit my alma mater and saw her again. She had been promoted and was very busy. I waited for her to finish a meeting and we met briefly. She was pleasantly surprised, but time was too short, and we didn\u0026rsquo;t chat much.\nThe saying \u0026ldquo;time spares no one\u0026rdquo; is cliché, but it is truly the case. At a glance, her hair was noticeably whiter, and a few deep wrinkles had been added to her face (my dad is the same way, haha). It made me feel that the years have indeed marched through our lives.\nI don\u0026rsquo;t remember what else we talked about, I only remember her saying: \u0026ldquo;Do you remember this? (She showed me the hairpin in her hair.) I\u0026rsquo;ve worn this hairpin for three years. Every time I see it, I talk to everyone about the student who gave it to me, what he was like back then.\u0026rdquo;\nTo be honest, I was shocked at the time. How should I put it? Yes, a mix of emotions, because I myself had almost forgotten about it.\nI could imagine how she used me as the protagonist of her stories in class, just as I grew up listening to her tell stories about others.\nMy writing isn\u0026rsquo;t that great, please forgive me. Why does this feel a bit like the plot of a cliché romance novel, hahaha.\nAfter a few brief sentences, she rushed off to another meeting.\nI am indeed a bit nostalgic, but memories do get beautified. Many details are forgotten, as are the feelings at the exact moment.\nAnyway, writing this down, I am missing the openness of middle school. At that time, I was a day student, laughing and fooling around with good friends on the way home from school every day.\nSchool ended early back then, around 6 o\u0026rsquo;clock. Although my home was quite far and it took an hour to walk, on the way back, I could watch the sky gradually darken and the streetlights slowly turn on. Sometimes, if I lost my bus fare or spent it on snacks, I had no choice but to walk. When walking alone, I always liked to let my mind wander, or just empty my head and think of nothing, just walking. Once home, I could eat, though unfortunately, when my mom wasn\u0026rsquo;t home, the food wasn\u0026rsquo;t very tasty.\nThere was a road on the way home that ran east-west, facing directly into the sun. The road was very wide. At sunset, walking on that path meant facing the sun directly; it was very round and very large. Although it was hot in the summer, it was incredibly comfortable in the winter.\nThis feeling of loneliness yet freedom is something I miss very much.\nOf course, middle school life wasn\u0026rsquo;t as illusory and beautiful as the memory filter makes it out to be. Back then, because I was naughty, I often got the rod, and I argued with my family too. Thinking back now, I really was disobedient and did some outrageous things (once I stayed out in the neighborhood until nearly midnight without telling my family, causing them to search frantically for me).\nBy the time I reached high school, the atmosphere became much more oppressive. Time was squeezed dry by academics, and the classes were too boring to even mention.\nBut one person who left a deep impression was our chemistry teacher. He taught extremely well and guided us to think, and the knowledge wasn\u0026rsquo;t just confined to the classroom (although the ultimate goal was still geared toward the exams). His experiments were also very interesting. After all, real experiments can\u0026rsquo;t always replicate textbooks perfectly, but he would lead us to analyze the causes, read papers to study the \u0026ldquo;why,\u0026rdquo; and tell us: exam points are hardcoded into the books, but knowledge is not hardcoded.\nFor the three years of high school, unlike middle school, we could only hole up in the classroom, going home maybe a few times a month (no one was home, and my dad\u0026rsquo;s cooking was bad anyway, haha). This period shaped a huge part of my current worldview and values. It feels like it was just a few years ago.\nDuring high school, I became addicted to watching anime. I watched everything. Even without a phone or a computer, I could find ways to watch. There\u0026rsquo;s always a way.\nI watched a ton of anime back then (and still do, of course), some of which influence me to this day.\nLet\u0026rsquo;s talk about one anime that had a major impact on me: Oregairu (My Youth Romantic Comedy Is Wrong, As I Expected).\nI think I watched this anime at exactly the right time. Perhaps youth just needs Hachiman (Da Laoshi) to tell you some seemingly correct fallacies to break the inherent concepts you\u0026rsquo;ve held since childhood.\nPerhaps it\u0026rsquo;s the carefully crafted solitary image of Hachiman resonating with the rebellious nature of adolescent youth. He does some seemingly cool things in his own way, and the similarities between you and him make you involuntarily step into his shoes and experience his feelings together. This profound sense of resonance makes you sink into it, and the philosophical logic the author occasionally throws in further strengthens your immersion.\nIt\u0026rsquo;s probably only when you are fifteen or sixteen years old that you can fall so deeply into it.\nUltimately, I am not Hachiman. I don\u0026rsquo;t have a Service Club, nor do I have a Komachi. I was obsessed with it for a while, but after figuring it out, I stopped caring so much.\nI watched few anime back then, so I couldn\u0026rsquo;t really claim to have critical appraisal skills, but luckily, what I encountered were excellent works. Later on, I watched a lot of random stuff, and I wasn\u0026rsquo;t negatively influenced; rather, my worldview became even clearer.\nBesides studying, the rest of the time at school was spent reading extracurricular books (PE classes were short, and the damn school gym was closed on weekends). I read many, many novels at the time, mostly modern and contemporary literature, covering a wide variety of genres, both Chinese and Western.\nTo name a few that left a deep impression on me: domestically, Shi Tiesheng\u0026rsquo;s Notes on Principles (务虚笔记), Yu Hua\u0026rsquo;s The Seventh Day, Yu Qiuyu\u0026rsquo;s A Bitter Journey of Culture (this one is highly controversial), Wang Xiaobo (the Trilogy of Ages), Lu Xun, Lao She, Mo Yan, and so on.\nFrom Japan: Natsume Soseki (I Am a Cat and Kokoro), Ryunosuke Akutagawa (Rashomon), Naoya Shiga, Yasunari Kawabata, Haruki Murakami, etc.\nAs for Western authors, there are too many, and I don\u0026rsquo;t remember many of them. Just to list a few: Camus, Milan Kundera, Hemingway, Shakespeare, Márquez\u0026hellip; way too many.\nOf course, it wasn\u0026rsquo;t all so-called \u0026ldquo;highbrow\u0026rdquo; literature; there was popular literature (light novels) too, though I didn\u0026rsquo;t read much domestic web fiction or outrageous romance novels. I was also deeply engrossed in authors like Jin Yong, Jiang Nan (Dragon Raja), and mystery novels.\nListing so many, I\u0026rsquo;m actually just recommending works (doge).\nAlthough I mostly just skimmed through them, swallowing dates whole without seeking profound understanding. I didn\u0026rsquo;t read to fully comprehend these books; my life experience was far from enough to truly understand a genuinely good book. Perhaps it was purely to kill time, or for the resonance between the words and the depths of my heart, or perhaps, simply because I liked books.\nPeople always love to praise Shi Tiesheng\u0026rsquo;s spirit of facing death directly, but other than The Temple of Earth and I from the textbooks, how many have truly read his works? Are his reflections on life in Notes on Principles and Fragments Written in Sickness really just empty talk about being born an \u0026ldquo;iron man\u0026rdquo;? Textbooks stereotype articles, and dogmatic appreciation only forces you to fit into the examiner\u0026rsquo;s testing points. You don\u0026rsquo;t need to express your personal insights; you just need to fill in the template-like nonsense perfectly. With this kind of routine, how can anyone experience the suffocating feeling of their heart being gripped tight when reading Notes on Principles? Literature inherently has no standard answers, yet education tries to cram it into standard answers. So it is regrettable that sitting in a classroom, one actually fails to learn \u0026ldquo;Language and Literature\u0026rdquo;.\nReading 1984, I was deeply shocked by the world the author created. I sincerely felt terrified by the oligarchy in the book: history can be fabricated at will, humans can be domesticated into \u0026ldquo;living creatures\u0026rdquo; possessing only biological traits, and what we call relationships between people can be completely severed. The phrase \u0026ldquo;Big Brother is watching you\u0026rdquo; lives on forever, and not without reason.\nIn addition, the imagination in Liu Cixin\u0026rsquo;s The Three-Body Problem is truly at the pinnacle. Who knows how many interesting ideas Liu Cixin exhausted for it. I also read many international sci-fi award-winning works, including epics like Foundation (Galactic Empire).\nThe depiction of emotions in The Shadow Thief is very delicate, even moving, which is why I like Marc Levy\u0026rsquo;s works.\nAnd The Catcher in the Rye and To Kill a Mockingbird made me think about my own future and family.\nThere was also a book back then that shattered my perception of language learning, unfortunately, I can\u0026rsquo;t remember the title. I vaguely remember the contents being related to the \u0026ldquo;acquisition\u0026rdquo; theory.\nDuring that time, I would ask myself: What are you interested in? What do you like? What do you hate? What do you want to do?\nAt that time, I told myself: Do more interesting things, meet more interesting people, don\u0026rsquo;t regret the choices you\u0026rsquo;ve made, and always maintain your curiosity.\nTo this day, I still romantically believe that \u0026ldquo;there must be a reason why I am here.\u0026rdquo;\nThe influence these works had on me was subtle and imperceptible, and I can\u0026rsquo;t quite articulate it clearly. But I always feel that if I hadn\u0026rsquo;t read these books, I would no longer be me.\nYet sadly, once these living, flesh-and-blood works enter the classroom, all their moisture is drained dry. Dogmatic appreciation forces everything into templates, and answering questions turns into bootlicking the test points. You don\u0026rsquo;t need to feel any visceral pain; just apply the template rules one by one, and you\u0026rsquo;ll eventually be right.\nEspecially for essays. Under the sun, must everything written by every student be exactly the same as the model essay? Must everyone use the same source material? Must everyone discuss the same argument and propose the same viewpoint? If your angle is different from what the examiner wants, you get no points?\nIn my view, the angles that can be abstracted from these prompts are either too simple and direct, or incredibly abstract, meaning you absolutely have to align your brainwaves with the examiner to get it right.\nI also can\u0026rsquo;t understand high-scoring essays. I admire how they can use so much source material, angles, and flowery rhetoric to argue an obviously redundant point.\nThinking back to when squeezing out an 800 or 1,000-word essay was agonizing, now I can easily write a thousands-of-words tirade, even if it lacks structure.\nFor me, truths I agree with, I will naturally follow; truths I disagree with, no matter who tries to lecture me, it\u0026rsquo;s useless. Those template essays cannot touch people\u0026rsquo;s hearts (AI can easily generate such text now, anyway). And I believe that the truly crucial core of life lies exactly in the principles that others cannot teach you, that you can only realize yourself.\nI just outputted a bunch of outrageous hot takes, but I\u0026rsquo;ve held it in for a long time, and writing it out feels much better.\nI know very well that listing all those book titles earlier runs the risk of seeming a bit pedantic and show-offy. It\u0026rsquo;s not that I possess profound knowledge; it\u0026rsquo;s just that my own thin thoughts and reflections are not enough to pierce through this heavy \u0026ldquo;Iron House\u0026rdquo; (Lu Xun\u0026rsquo;s metaphor), so I had to wave the banners of my predecessors to bolster my voice.\nToday, having reached university, my understanding of \u0026ldquo;exams and education\u0026rdquo; has become thoroughly clear.\nI resent this evaluation system that relies solely on scores and GPA. It is like a massive, precise meat grinder, grinding all raw youth and burgeoning thirst for knowledge into uniform minced meat. You shouldn\u0026rsquo;t ask why you have to learn it; just obey. You cannot afford the leisure of straying from the test points, otherwise you are an anomaly.\nBut I cannot mock those classmates who desperately grasp for high scores and fight for postgraduate recommendation spots. They stare intensely at the carrot named \u0026ldquo;future\u0026rdquo; dangling in front of them, running desperately round and round the millstone named \u0026ldquo;GPA.\u0026rdquo; In this cramped world, there are mostly just ordinary people trying to secure a bit of stability and dignity. Since the machine only recognizes this stamp of approval, who can blame them for having to compromise just to make a living? Most of us are merely victims within this absurd system, crushing each other, yet each holding our own bitterness.\nI am a loser under this system, but I ultimately cannot swallow this resentment. It’s one thing if the class is taught poorly, but they force you to read heavily-recycled, ancient PPTs and listen to condescending lectures, all under the guise of \u0026ldquo;it\u0026rsquo;s for your own good.\u0026rdquo; What exactly is good?\nStudents have a clear scale in their own hearts.\nIf you ask me, taking a wild, unorthodox path that no one around cares about, abandoning the visible \u0026ldquo;certainties\u0026rdquo; to pursue so-called passion and love—does it guarantee a good ending?\nI don\u0026rsquo;t know.\nI have no certainty in my heart. Maybe one day, I\u0026rsquo;ll crash and burn, tumbling into an even deeper quagmire. But if I stay where I am, I might suffocate myself to death.\nThere will always be a gap between reality and the ideal. Striving with all my might to pursue utopia—this is all I can do.\nI am a very ordinary person. I probably realized long ago that there is a huge gap between me and others, but my heart is always unwilling to accept it, so I bury my head in hard work, occasionally paralyzing my nerves with retaliatory entertainment.\nI don\u0026rsquo;t demand that the things I learn must yield some specific result; I only hope that I can properly cherish the substance they have already given me.\nI love pure passion. I remember when I was obsessed with Gil Strang\u0026rsquo;s Linear Algebra course, sleeping at 3 AM and waking up at 11 AM to grind extra training, thinking from morning till night about getting the Lab to run successfully. All of that was truly pure and joyful.\nListen to everyone\u0026rsquo;s voices:\nManshi is a popular science uploader I really like, as well as Bidao, 3b1b, and Veritasium.\nWhat Do You Want to Do Where Is the Problem With Our Engineering Education Four Years at Nanjing University - Software Engineering and Armchair Strategies How Mentor Green Got Cuckolded: The Origins of the Academic Leap Forward Movement Essay No. 1 $upd:$\nI am very grateful you read this far, I didn\u0026rsquo;t think anyone would read it (just kidding). Made minor edits, I was a bit agitated at the time.\nI sincerely hope that everyone can see their true selves clearly and preserve a bit of pure, personal fanaticism that belongs solely to you.\nP.S. The English text above was translated by a Large Language Model without manual proofreading. Please excuse any unnatural phrasing or slight losses in the original emotional nuance.\n","date":"2025-12-25T21:28:01+08:00","image":"https://i.111666.best/image/kQpaR3pgGDE3cQ5rOcM47u.png","permalink":"https://anfsity.com/en/p/essay-no.-2/","title":"Essay No. 2"},{"content":"Install binder We need to install binder because Arknights depends on it to translate app messages to the Linux system.\nLinux-zen is an alternative kernel available in the official Arch repos.\n$$ \\text{Arknights} \\xrightarrow{\\text{needs}} \\text{Android OS} \\xrightarrow{\\text{needs}} \\text{Binder IPC} \\xrightarrow{\\text{needs}} \\text{Host Kernel Support} $$We can use pacman to install it:\n1 sudo pacman -S linux-zen linux-zen-headers If you use an NVIDIA GPU, you may need extra effort to make it work. Since I use an AMD GPU, I don\u0026rsquo;t really care about that.\nAfter installing linux-zen, if you use GRUB, you should use the instructions below to update your config before rebooting.\n1 sudo grub-mkconfig -o /boot/grub/grub.cfg If you use systemd-boot, it will update automatically, but you might need to check your loader entries in /boot/loader/entries to make sure linux-zen is selected.\nYou can use this command to check it:\n1 uname -r Waydroid We use Waydroid as the Android emulator for playing Arknights.\nUse pacman to download Waydroid.\nRun waydroid init to download the image. If installation fails due to network issues, you can use the archlinuxcn repo to install waydroid-image.\nThen run waydroid init again.\nDownload the waydroid script to install the Arm translation layer.\nTo improve translation performance, it is recommended to use libndk on AMD CPUs and libhoudini on Intel CPUs. However, some apps only support one specific translation layer, so if a game doesn\u0026rsquo;t work or has terrible performance, you might need to try both layers.\nRequires a Python virtual environment.\nInstall libndk arm translation layer\n1 sudo python3 main.py install libndk Install libhoudini arm translation layer\n1 sudo python3 main.py install libhoudini Only libhoudini works on my computer; libndk causes a black screen.\nIf installation fails, it might be due to network issues. Export the ports:\n1 2 3 export http_proxy=http://127.0.0.1:7897 export https_proxy=http://127.0.0.1:7897 export all_proxy=socks5://127.0.0.1:7897 Install via proxy:\n1 sudo -E venv/bin/python main.py Check Android version:\n1 waydroid prop get ro.build.version.release Setting Waydroid Resolution I use hyprland and haven\u0026rsquo;t found a good way to make the interface adapt to tiled window sizes automatically. I can only add a floating property to this window.\nDefine custom rules for waydroid:\n1 windowrulev2 = size 1600 900, float, class:^(Waydroid)$ You can adjust the width, height, and DPI:\n1 2 3 4 sudo waydroid prop set persist.waydroid.width 576 sudo waydroid prop set persist.waydroid.height 1024 sudo waydroid shell wm density 250 # Display looks good at 250 My advice is not to set these manually; let hyprland handle it.\nGoogle Play I originally wanted to set up Google Play, but there seems to be an issue on Google\u0026rsquo;s end, so I have to wait for a fix.\nDiscussion thread: Unable to register device in Google uncertified registration page .\nBUG Unknown Bug This is likely due to the audio server dying \u0026hellip; see Issue 576 and Issue 829 for details.\nA workaround is to run:\n1 # sysctl -w kernel.pid_max=65535 You can make it permanent by creating a .conf file in /etc/sysctl.d/ and adding kernel.pid_max=65535 to it.\n1 2 /etc/sysctl.d/99-sysctl.conf kernel.pid_max=65535 Hard to comment, hard to fix.\nThe cause hasn\u0026rsquo;t been located yet, and it\u0026rsquo;s not fixed. My guess is it\u0026rsquo;s an audio issue.\nDocker Disables IP Forwarding waydroid couldn\u0026rsquo;t connect to the internet (ping packet loss). Initially, I suspected it was a TUN mode issue, but after troubleshooting, it still couldn\u0026rsquo;t connect.\n1 2  sysctl net.ipv4.ip_forward net.ipv4.ip_forward = 1 When investigating the IP issue, I was told it might be caused by a conflict between Docker and waydroid.\nDocker changes the iptables forwarding policy to DROP by default:\n1 2 3 4 5 6  sudo iptables -nvL FORWARD [sudo] password for anfsity: Chain FORWARD (policy DROP 1735 packets, 177K bytes) pkts bytes target prot opt in out source destination 1735 177K DOCKER-USER all -- * * 0.0.0.0/0 0.0.0.0/0 1735 177K DOCKER-FORWARD all -- * * 0.0.0.0/0 0.0.0.0/0 Modify the forwarding policy:\n1 2 3 4 5 6 7 8 9 10 11  sudo iptables -I FORWARD 1 -i waydroid0 -j ACCEPT  sudo iptables -I FORWARD 1 -o waydroid0 -j ACCEPT  sudo iptables -t nat -A POSTROUTING -s 192.168.240.0/24 -o wlan0 -j MASQUERADE # Enable NAT  sudo iptables -nvL FORWARD Chain FORWARD (policy DROP 1973 packets, 212K bytes) pkts bytes target prot opt in out source destination 0 0 ACCEPT all -- * waydroid0 0.0.0.0/0 0.0.0.0/0 28 1680 ACCEPT all -- waydroid0 * 0.0.0.0/0 0.0.0.0/0 1973 212K DOCKER-USER all -- * * 0.0.0.0/0 0.0.0.0/0 1973 212K DOCKER-FORWARD all -- * * 0.0.0.0/0 0.0.0.0/0 Fix successful:\n1 2 3 4 5 6 7 8 9 10  sudo waydroid shell [sudo] password for anfsity: :/ # ping bing.com PING bing.com (150.171.28.10) 56(84) bytes of data. 64 bytes from 150.171.28.10: icmp_seq=1 ttl=114 time=71.9 ms 64 bytes from 150.171.28.10: icmp_seq=2 ttl=114 time=89.8 ms ^C --- bing.com ping statistics --- 2 packets transmitted, 2 received, 0% packet loss, time 1001ms rtt min/avg/max/mdev = 71.993/80.922/89.851/8.929 ms Save the rules after success:\n1 2 sudo iptables-save | sudo tee /etc/iptables/iptables.rules sudo systemctl enable --now iptables Shortcuts Use ydotool\n1 sudo pacman -S ydotool bc Since I have dual monitors, the hyprland pixel coordinates and ydotool pixel coordinates are different. Testing this makes me want to puke 🤮. Put on hold for now.\nOther Knowledge waydroid shell is similar to adb shell, but since it\u0026rsquo;s a container, it\u0026rsquo;s faster than ADB and has higher privileges.\nwaydroid file paths are stored in .local/share/waydroid/data/media/0\nAccess requires root privileges:\n1 2 3 4 5 6 7 8 9  ls .local/share/waydroid/data/media/0 \u0026#34;.local/share/waydroid/data/media/0\u0026#34;: Permission denied (os error 13)  sudo ls .local/share/waydroid/data/media/0 [sudo] password for anfsity: Alarms\tAudiobooks Documents\tMovies\tNotifications Podcasts Ringtones Android DCIM\tDownload\tMusic\tPictures Recordings  sudo ls .local/share/waydroid/data/media/0/Download app-release.apk\tclash-for-android.apk\tNeteaseCloudMusic_Music_official_9.4.15.251120174454_32614.apk clash-for-android-1.apk netease\tTapTap_2.89.0-rel.100100_rep.apk You can install APKs via the terminal:\n1 waydroid app install /path/to/your-app.apk List installed apps:\n1 waydroid app list Debug log information:\n1 2 3 4 5 waydroid logcat # Only show errors waydroid logcat *:E # Use grep to filter info waydroid logcat | grep \u0026#34;com.bilibili\u0026#34; Besides using logcat, since the kernel is shared, you can also use dmesg to capture logs.\n1 sudo dmesg -w | grep -iE \u0026#34;waydroid|binder|lxc\u0026#34; The Android configuration file is located at /var/lib/waydroid/waydroid_base.prop.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 cat /var/lib/waydroid/waydroid_base.prop sys.use_memfd=true ro.adb.secure=1 ro.debuggable=0 gralloc.gbm.device=/dev/dri/renderD128 debug.stagefright.ccodec=0 ro.hardware.gralloc=gbm ro.hardware.egl=mesa ro.hardware.vulkan=radeon ro.hardware.camera=v4l2 ro.opengles.version=196610 waydroid.updater.disabled=true waydroid.tools_version=1.6.0 ro.vndk.lite=true ro.product.cpu.abilist=x86_64,x86,arm64-v8a,armeabi-v7a,armeabi ro.product.cpu.abilist32=x86,armeabi-v7a,armeabi ro.product.cpu.abilist64=x86_64,arm64-v8a ro.dalvik.vm.native.bridge=libhoudini.so ro.enable.native.bridge.exec=1 ro.dalvik.vm.isa.arm=x86 ro.dalvik.vm.isa.arm64=x86_64 waydroid has two UI modes: Multi-Window and Full-UI.\nMulti-window allows apps to become independent wayland windows.\nFull-UI renders a complete Android desktop.\nReference Links waydroid docs archwiki waydroid $upd:$\nThere are just too many bugs. I don\u0026rsquo;t strictly need to game on Linux anyway, so I\u0026rsquo;m ditching waydroid. R.I.P.\n","date":"2025-12-02T17:54:36+08:00","permalink":"https://anfsity.com/en/p/play-arknights-in-archlinux/","title":"Play Arknights In ArchLinux"},{"content":" 这是 Lewis Baker 关于 C++ 协程系列文章的第一篇 。\nThis is the first of a series of posts on the C++ Coroutines TS , a new language feature that is currently on track for inclusion into the C++20 language standard.\n这是关于 C++ Coroutines TS 系列博文的第一篇，一个崭新的语言特性正在按计划纳入 C++20 的语言标准。\nIn this series I will cover how the underlying mechanics of C++ Coroutines work as well as show how they can be used to build useful higher-level abstractions such as those provided by the cppcoro library.\n在这个系列中，我将会涵盖 C++ 协程如何工作的底层机制，同时展示如何利用他们构建一个有用的如由 cppcoro 库提供的高层抽象。\nIn this post I will describe the differences between functions and coroutines and provide a bit of theory about the operations they support. The aim of this post is introduce some foundational concepts that will help frame the way you think about C++ Coroutines.\n在这篇博客中，我将会描述函数和协程之间的不同，并且提供一点点他们支持操作的理论。这篇博客的目的是介绍一些基础概念来帮助你形成思考 C++ 协程的方式。\nCorountines are Functions are Coroutines A coroutine is a generalisation of a function that allows the function to be suspended and then later resumed.\n一个协程是一个能让函数被挂起然后恢复的泛化函数。\nI will explain what this means in a bit more detail, but before I do I want to first review how a “normal” C++ function works.\n我将会更加详细的解释这意味着什么，但是在我解释之前，我希望先来复习一下一个 “普通的” c++ 函数是如何工作的。\n\u0026ldquo;Normal\u0026rdquo; Functions A normal function can be thought of as having two operations: Call and Return (Note that I’m lumping “throwing an exception” here broadly under the Return operation).\n一个普通的函数可以可以被认为有两种操作：Call 和 Return （注意我在这里把 “抛出异常” 归结为广泛的 Return 操作）。\nThe Call operation creates an activation frame, suspends execution of the calling function and transfers execution to the start of the function being called.\nCall 操作创建一个激活帧，暂停调用函数的执行并且转移执行权到被调用函数的开始。\nThe Return operation passes the return-value to the caller, destroys the activation frame and then resumes execution of the caller just after the point at which it called the function.\nReturn 操作将返回值传递给调用者，销毁激活帧，然后紧接着在调用该函数的位置之后恢复调用者的执行。\nLet’s analyse these semantics a little more…\n让我们进一步分析这些语义。。。\nActivation Frames So what is this ‘activation frame’ thing?\n所以，这个 “激活帧” 到底是什么呢？\nYou can think of the activation frame as the block of memory that holds the current state of a particular invocation of a function. This state includes the values of any parameters that were passed to it and the values of any local variables.\n你可以把激活帧视作一块维护当前特定函数调用状态的内存。这个状态包括被传入函数的参数的值和其他任何局部变量。\nFor “normal” functions, the activation frame also includes the return-address - the address of the instruction to transfer execution to upon returning from the function - and the address of the activation frame for the invocation of the calling function. You can think of these pieces of information together as describing the ‘continuation’ of the function-call. ie. they describe which invocation of which function should continue executing at which point when this function completes.\n对 “普通” 函数来说，激活帧也包括返回地址 - 即从函数返回后，(CPU) 要执行指令的地址 - 和调用函数此次调用的激活帧地址。你可以认为这些信息共同描述了函数调用的“延续性”。也就是说，他们描述了哪个函数的哪个调用应该在函数完成的哪个位置继续执行。\nWith “normal” functions, all activation frames have strictly nested lifetimes. This strict nesting allows use of a highly efficient memory allocation data-structure for allocating and freeing the activation frames for each of the function calls. This data-structure is commonly referred to as “the stack”.\n对于 “普通” 函数，每个激活帧都有严格嵌套的生命周期。这种严格嵌套的关系，使得高效率内存分配数据结构的分配和释放每个函数调用的激活帧得以成立。这种数据结构通常被称为 “栈”。\nWhen an activation frame is allocated on this stack data structure it is often called a “stack frame”.\n当激活帧被分配到这个栈数据结构时，通常被称为 “栈帧”。\nThis stack data-structure is so common that most (all?) CPU architectures have a dedicated register for holding a pointer to the top of the stack (eg. in X64 it is the rsp register).\n这种栈数据结构是如此的常见，以至于绝大多数 CPU 架构都有一个专用的寄存器来保存栈顶指针（如：X64 的 rsp 寄存器）。\nTo allocate space for a new activation frame, you just increment this register by the frame-size. To free space for an activation frame, you just decrement this register by the frame-size.\n为了给新的激活帧分配空间，你只需给这个寄存器的值加上帧大小。为了释放激活帧的空间，你只需给这个寄存器的值减去帧大小。\nThe \u0026lsquo;Call\u0026rsquo; Operation When a function calls another function, the caller must first prepare itself for suspension.\n当一个函数调用另一个函数时，调用者必须先为挂起做好准备。\nThis ‘suspend’ step typically involves saving to memory any values that are currently held in CPU registers so that those values can later be restored if required when the function resumes execution. Depending on the calling convention of the function, the caller and callee may coordinate on who saves these register values, but you can still think of them as being performed as part of the Call operation.\n这个 “挂起” 步骤通常涉及将当前 CPU 寄存器的值保存进内存，以便稍后函数恢复执行时，能够在需要的情况下还原这些值。取决于函数的调用约定，调用者和被调用者也许会协调谁来保存这些值，但你仍然可以将这一过程视为调用操作的一部分。\nThe caller also stores the values of any parameters passed to the called function into the new activation frame where they can be accessed by the function.\n调用者会将被传进被调用函数的参数值储存进一个新的可以被函数访问的激活帧。\nFinally, the caller writes the address of the resumption-point of the caller to the new activation frame and transfers execution to the start of the called function.\n最终，调用者将调用者的恢复点地址写入新的激活帧，并且转移执行到被调用函数的开始。\nIn the X86/X64 architecture this final operation has its own instruction, the call instruction, that writes the address of the next instruction onto the stack, increments the stack register by the size of the address and then jumps to the address specified in the instruction’s operand.\n在 X86/X64 架构中，这个最终操作有他自己的指令 - call 指令。这个指令将下一个指令的地址写入栈中，给栈指针的值加上地址大小，然后跳转到在指令操作数里面指定的地址。\nThe \u0026lsquo;Return\u0026rsquo; Operation When a function returns via a return-statement, the function first stores the return value (if any) where the caller can access it. This could either be in the caller’s activation frame or the function’s activation frame (the distinction can get a bit blurry for parameters and return values that cross the boundary between two activation frames).\n当函数经过 return 语句时返回时，函数首先把返回值（如果存在）存进一个调用者可以访问的位置。这个位置可以是调用者的激活帧或者函数的激活帧（当参数和返回值跨越两个激活帧的边界时，界限可能会有些模糊）。\nThen the function destroys the activation frame by:\nDestroying any local variables in-scope at the return-point. Destroying any parameter objects Freeing memory used by the activation-frame 然后函数通过以下方式销毁激活帧：\n销毁所有在 return-point 作用域内的局部变量。 销毁所有参数对象。 释放激活帧占用的内存。 And finally, it resumes execution of the caller by:\nRestoring the activation frame of the caller by setting the stack register to point to the activation frame of the caller and restoring any registers that might have been clobbered by the function. Jumping to the resume-point of the caller that was stored during the ‘Call’ operation. 最终，调用者通过：\n通过设置栈指针指向调用者的激活帧，来还原调用者的激活帧。还原那些可能被函数覆写后的寄存器。 跳转到在 Call 操作中储存的调用者恢复点。 恢复调用者执行。\nNote that as with the ‘Call’ operation, some calling conventions may split the responsibilities of the ‘Return’ operation across both the caller and callee function’s instructions.\n请注意，与“调用”操作类似，某些调用约定也许会将“返回”操作的责任分割到调用者和被调用者的指令中。\nCoroutines Coroutines generalise the operations of a function by separating out some of the steps performed in the Call and Return operations into three extra operations: Suspend, Resume and Destroy.\n协程通过把函数操作内调用和返回的部分执行步骤细分， 将其泛化为三个额外的操作：挂起，恢复，销毁。\nThe Suspend operation suspends execution of the coroutine at the current point within the function and transfers execution back to the caller or resumer without destroying the activation frame. Any objects in-scope at the point of suspension remain alive after the coroutine execution is suspended.\n挂起操作在函数内部的当前位置暂停协程的执行，同时在不销毁激活帧的情况下，将执行权转移回调用者 (caller) 或恢复者 (resumer)。在协程执行挂起后，任何在挂起点作用域内的对象都会保持存活。\nNote that, like the Return operation of a function, a coroutine can only be suspended from within the coroutine itself at well-defined suspend-points.\n注意到如同函数的返回操作，一个协程仅能在协程内部的一个明确定义的挂起点被挂起。\nThe Resume operation resumes execution of a suspended coroutine at the point at which it was suspended. This reactivates the coroutine’s activation frame.\n恢复操作在挂起点恢复执行协程。这重新激活了协程的激活帧。\nThe Destroy operation destroys the activation frame without resuming execution of the coroutine. Any objects that were in-scope at the suspend point will be destroyed. Memory used to store the activation frame is freed.\n销毁操作销毁销毁协程的激活帧，而不再恢复协程的执行。任何在挂起点作用域内的对象都将被销毁。激活帧占用的内存也会被释放。\nCoroutine activation frames Since coroutines can be suspended without destroying the activation frame, we can no longer guarantee that activation frame lifetimes will be strictly nested. This means that activation frames cannot in general be allocated using a stack data-structure and so may need to be stored on the heap instead.\n因为协程可以不销毁激活帧挂起，所以我们不能再保证激活帧的生命周期是严格嵌套的。这意味着激活帧通常无法使用栈数据结构来分配内存，因此可能需要分配在堆上。\nThere are some provisions in the C++ Coroutines TS to allow the memory for the coroutine frame to be allocated from the activation frame of the caller if the compiler can prove that the lifetime of the coroutine is indeed strictly nested within the lifetime of the caller. This can avoid heap allocations in many cases provided you have a sufficiently smart compiler.\n在 C++ Coroutines TS 中有些机制，如果编译器能够证明协程的生命周期确实是严格嵌套在调用者的生命周期内部的，可以允许协程帧的内存分配到调用者的激活帧上。只要你有一个足够聪明的编译器，这可以在很多情况下避免堆分配。\nWith coroutines there are some parts of the activation frame that need to be preserved across coroutine suspension and there are some parts that only need to be kept around while the coroutine is executing. For example, the lifetime of a variable with a scope that does not span any coroutine suspend-points can potentially be stored on the stack.\n对于协程，激活帧的某些部分需要在协程挂起期间被保留，而有些部分只需要在协程执行期间存在。例如，如果一个变量的作用域没有横跨任何协程挂起点，那么他就有可能被储存在栈上面。\nYou can logically think of the activation frame of a coroutine as being comprised of two parts: the ‘coroutine frame’ and the ‘stack frame’.\n你可以在逻辑上认为协程的激活帧由两个部分组成：“协程帧”和“栈帧”。\nThe ‘coroutine frame’ holds part of the coroutine’s activation frame that persists while the coroutine is suspended and the ‘stack frame’ part only exists while the coroutine is executing and is freed when the coroutine suspends and transfers execution back to the caller/resumer.\n“协程帧”保存了协程激活帧中在协程挂起期间依然持久存在的那部分数据，而“栈帧”部分仅在协程执行期间存在，当协程挂起并将执行权交还给调用者/恢复者时，这部分就会被释放。\nThe ‘Suspend’ operation The Suspend operation of a coroutine allows the coroutine to suspend execution in the middle of the function and transfer execution back to the caller or resumer of the coroutine.\n协程的挂起操作允许协程在函数中间暂停执行，并且将执行权转移回协程的调用者或恢复者。\nThere are certain points within the body of a coroutine that are designated as suspend-points. In the C++ Coroutines TS, these suspend-points are identified by usages of the co_await or co_yield keywords.\n在协程的内部有些位置被指定为挂起点。在 C++ Coroutines TS 中，这些挂起点通过 co_await 和 co_yield 关键字识别。\nWhen a coroutine hits one of these suspend-points it first prepares the coroutine for resumption by:\nEnsuring any values held in registers are written to the coroutine frame Writing a value to the coroutine frame that indicates which suspend-point the coroutine is being suspended at. This allows a subsequent Resume operation to know where to resume execution of the coroutine or so a subsequent Destroy to know what values were in-scope and need to be destroyed. 当协程执行到了这些挂起点之一时，它首先会通过以下方式为恢复协程做准备：\n确保所有储存在寄存器里面的值被写进协程帧。 向协程帧里面写入一个值，表明在协程里面的哪些一个挂起点正在被挂起。这保证了后续的恢复操作知道协程在哪里恢复执行，或者让后续的销毁操作知道哪些值在作用域内，需要被销毁。 Once the coroutine has been prepared for resumption, the coroutine is considered ‘suspended’.\n一旦协程完成了恢复准备，这个协程就被认为是“挂起”。\nThe coroutine then has the opportunity to execute some additional logic before execution is transferred back to the caller/resumer. This additional logic is given access to a handle to the coroutine-frame that can be used to / later resume or destroy it.\n然后协程在执行权被转移回调用者/恢复者之前，有机会去执行一些额外的逻辑。这些额外的逻辑能够访问一个句柄，该句柄后续能被用来恢复或者销毁协程帧。\nThis ability to execute logic after the coroutine enters the ‘suspended’ state / allows the coroutine to be scheduled for resumption without the need for synchronisation / that would otherwise be required if the coroutine was scheduled for resumption prior to entering the ‘suspended’ state / due to the potential for suspension and resumption of the coroutine to race. I’ll go into this in more detail in future posts.\n这种协程在进入“挂起”状态后仍能执行逻辑的能力，允许我们为协程的恢复进行调度，而不需要同步（如果我们为协程恢复调度先于进入“挂起”状态，由于协程潜在的挂起恢复竞争，会导致需要进行同步）。我会在未来的博客进一步深入讨论这个。\nThe coroutine can then choose to either immediately resume/continue execution of the coroutine or can choose to transfer execution back to the caller/resumer.\n随后，协程可以选择立即恢复/继续协程的执行，或者将执行权转移回调用者/恢复者。\nIf execution is transferred to the caller/resumer the stack-frame part of the coroutine’s activation frame is freed and popped off the stack.\n如果执行权转移回调用者/恢复者，协程激活帧的栈帧部分将会被释放，并从栈顶弹出。\nThe ‘Resume’ operation The Resume operation can be performed on a coroutine that is currently in the ‘suspended’ state.\n恢复操作可以针对当前处于“挂起”状态的协程来执行。\nWhen a function wants to resume a coroutine it needs to effectively ‘call’ into the middle of a particular invocation of the function. The way the resumer identifies the particular invocation to resume is by calling the void resume() method on the coroutine-frame handle provided to the corresponding Suspend operation.\n当一个函数想要恢复协程时，他实际上需要‘call’到函数特定调用的中间位置。恢复者识别这种特定的恢复调用的方式是，在那个由相应挂起操作提供的协程帧句柄上，调用 void resume() 方法。\nJust like a normal function call, this call to resume() will allocate a new stack-frame and store the return-address of the caller in the stack-frame before transferring execution to the function.\n就如普通的函数调用，resume() 的调用会分配一个新的栈帧，并在执行权被转移回函数之前，将调用者的返回地址储存到栈帧里面。\nHowever, instead of transferring execution to the start of the function it will transfer execution to the point in the function at which it was last suspended. It does this by loading the resume-point from the coroutine-frame and jumping to that point.\n然而，不同于将执行权转移回函数的开始，这个调用会把执行权转移到函数上一次被挂起的位置。它通过加载协程帧里的恢复位置并跳转到这个位置来实现这个操作。\nWhen the coroutine next suspends or runs to completion this call to resume() will return and resume execution of the calling function.\n当协程的下次挂起或运行完成时，resume() 的调用将会返回，并恢复调用函数的执行。\nThe ‘Destroy’ operation The Destroy operation destroys the coroutine frame without resuming execution of the coroutine.\n销毁操作销毁协程帧，而不恢复协程的执行。\nThis operation can only be performed on a suspended coroutine.\n这个操作仅能在被挂起的协程上执行。\nThe Destroy operation acts much like the Resume operation in that it re-activates the coroutine’s activation frame, including allocating a new stack-frame and storing the return-address of the caller of the Destroy operation.\n销毁操作在重新激活协程帧这一点上，与恢复操作十分类似，这包括分配一个新的栈帧和储存销毁操作调用者的返回地址。\nHowever, instead of transferring execution to the coroutine body at the last suspend-point it instead transfers execution to an alternative code-path that calls the destructors of all local variables in-scope at the suspend-point before then freeing the memory used by the coroutine frame.\n然而，不同于将执行权转移到函数上一个挂起点的协程内部，它把执行权转移到另一个代码路径上。在协程帧占用的内存被释放之前，这个代码路径调用挂起点处作用域内所有局部变量的析构函数。\nSimilar to the Resume operation, the Destroy operation identifies the particular activation-frame to destroy by calling the void destroy() method on the coroutine-frame handle provided during the corresponding Suspend operation.\n与恢复操作类似，销毁操作在相应挂起操作提供的协程帧句柄上，调用 void destroy() 方法，来识别将要被销毁的特定激活帧。\nThe ‘Call’ operation of a coroutine The Call operation of a coroutine is much the same as the call operation of a normal function. In fact, from the perspective of the caller there is no difference.\n协程的调用操作与普通函数的调用操作非常相似。事实上，从调用者的视角来看，这没有任何区别。\nHowever, rather than execution only returning to the caller when the function has run to completion, with a coroutine the call operation will instead resume execution of the caller when the coroutine reaches its first suspend-point.\n然而，函数只有运行完成后才将执行权返回给调用者，而协程不同，协程的调用操作会在协程到达第一个挂起点时，就恢复调用者的执行。\nWhen performing the Call operation on a coroutine, the caller allocates a new stack-frame, writes the parameters to the stack-frame, writes the return-address to the stack-frame and transfers execution to the coroutine. This is exactly the same as calling a normal function.\n当正在协程中执行调用操作时，调用者分配一个新的栈帧，将参数、返回地址写入栈帧，并转移执行权给协程。这与普通函数的调用是完全相同的。\nThe first thing the coroutine does is then allocate a coroutine-frame on the heap and copy/move the parameters from the stack-frame into the coroutine-frame so that the lifetime of the parameters extends beyond the first suspend-point.\n协程做的第一件事是，在堆上分配一个协程帧，把参数从栈帧复制/移动到协程帧上面，以便参数的生命周期能够延续到第一个挂起点之后。\nThe ‘Return’ operation of a coroutine The Return operation of a coroutine is a little different from that of a normal function.\n协程的返回操作和普通函数有稍微的不同。\nWhen a coroutine executes a return-statement (co_return according to the TS) operation it stores the return-value somewhere (exactly where this is stored can be customised by the coroutine) and then destructs any in-scope local variables (but not parameters).\n当协程执行一个 return 语句（依据 TS 是 co_return）时，它把返回值储存到某些地方（具体在哪些地方可以由协程自定义），然后析构在作用域内的所有局部变量（不包括参数）。\nThe coroutine then has the opportunity to execute some additional logic before transferring execution back to the caller/resumer.\n在转移执行权给调用者/恢复者之前，协程有机会去执行一些额外的逻辑。\nThis additional logic might perform some operation to publish the return value, or it might resume another coroutine that was waiting for the result. It’s completely customisable.\n这些额外的逻辑也许会执行一些操作去发布返回值，或者恢复另一个正在等待结果的协程。这完全是自定义的。\nThe coroutine then performs either a Suspend operation (keeping the coroutine-frame alive) or a Destroy operation (destroying the coroutine-frame).\n然后协程会执行挂起操作（保持协程帧存活）或者销毁操作（销毁协程帧）。\nExecution is then transferred back to the caller/resumer as per the Suspend/Destroy operation semantics, popping the stack-frame component of the activation-frame off the stack.\n随后，执行权会按照挂起/销毁操作的语义转移回调用者/恢复者，并将激活帧的栈帧部分从栈中弹出。\nIt is important to note that the return-value passed to the Return operation is not the same as the return-value returned from a Call operation as the return operation may be executed long after the caller resumed from the initial Call operation.\n很重要的一点是，传递给 Return 操作的返回值，和 Call 操作的返回的返回值是不一样的，因为 return 操作执行的时间点，可能比调用者从初始 Call 操作的时间点要晚很久。\nAn illustration To help put these concepts into pictures, I want to walk through a simple example of what happens when a coroutine is called, suspends and is later resumed.\n为了以图解的方式阐释这些概念，我希望带你梳理一些关于协程被调用，挂起，以及稍后被恢复时的简单例子。\nSo let’s say we have a function (or coroutine), f() that calls a coroutine, x(int a).\n假如我们有一个函数（或者协程）f(), 它调用一个协程 x(int a)。\nBefore the call we have a situation that looks a bit like this:\n在调用之前我们的（内存）情况大致如下：\n1 2 3 4 5 6 7 8 STACK REGISTERS HEAP +------+ +---------------+ \u0026lt;------ | rsp | | f() | +------+ +---------------+ | ... | | | Then when x(42) is called, it first creates a stack frame for x(), as with normal functions.\n然后当 x(42) 被调用，它首先为 x() 创建一个栈帧，如同普通的函数。\n1 2 3 4 5 6 7 8 9 10 STACK REGISTERS HEAP +----------------+ \u0026lt;-+ | x() | | | a = 42 | | | ret= f()+0x123 | | +------+ +----------------+ +--- | rsp | | f() | +------+ +----------------+ | ... | | | Then, once the coroutine x() has allocated memory for the coroutine frame on the heap and copied/moved parameter values into the coroutine frame we’ll end up with something that looks like the next diagram. Note that the compiler will typically hold the address of the coroutine frame in a separate register to the stack pointer (eg. MSVC stores this in the rbp register).\n然后，一旦协程 x() 为协程帧在堆上分配了内存，并复制/移动参数值到协程帧里，我们最终得到类似如下图表的情况。注意到编译器通常会把协程帧的地址保存在和栈指针不同的寄存器（eg. MSVC 把这个储存在 rbp 寄存器）。\n1 2 3 4 5 6 7 8 9 10 STACK REGISTERS HEAP +----------------+ \u0026lt;-+ | x() | | | a = 42 | | +--\u0026gt; +-----------+ | ret= f()+0x123 | | +------+ | | x() | +----------------+ +--- | rsp | | | a = 42 | | f() | +------+ | +-----------+ +----------------+ | rbp | ------+ | ... | +------+ | | If the coroutine x() then calls another normal function g() it will look something like this.\n如果协程 x() 接下来调用了另一个普通函数 g()，情况如下：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 STACK REGISTERS HEAP +----------------+ \u0026lt;-+ | g() | | | ret= x()+0x45 | | +----------------+ | | x() | | | coroframe | --|-------------------+ | a = 42 | | +--\u0026gt; +-----------+ | ret= f()+0x123 | | +------+ | x() | +----------------+ +--- | rsp | | a = 42 | | f() | +------+ +-----------+ +----------------+ | rbp | | ... | +------+ | | When g() returns it will destroy its activation frame and restore x()’s activation frame. Let’s say we save g()’s return value in a local variable b which is stored in the coroutine frame.\n当 g() 返回时，它将会摧毁自己的激活帧，并且恢复 x() 的激活帧。假设我们把 g() 的返回值保存在了储存在协程帧上的局部变量 b 上。\n1 2 3 4 5 6 7 8 9 10 STACK REGISTERS HEAP +----------------+ \u0026lt;-+ | x() | | | a = 42 | | +--\u0026gt; +-----------+ | ret= f()+0x123 | | +------+ | | x() | +----------------+ +--- | rsp | | | a = 42 | | f() | +------+ | | b = 789 | +----------------+ | rbp | ------+ +-----------+ | ... | +------+ | | If x() now hits a suspend-point and suspends execution without destroying its activation frame then execution returns to f().\n如果现在 x() 遇到了挂起点并挂起执行，且没有销毁其激活帧，那么执行权会返回给 f()。\nThis results in the stack-frame part of x() being popped off the stack while leaving the coroutine-frame on the heap. When the coroutine suspends for the first time, a return-value is returned to the caller. This return value often holds a handle to the coroutine-frame that suspended that can be used to later resume it. When x() suspends it also stores the address of the resumption-point of x() in the coroutine frame (call it RP for resume-point).\n这导致 x() 的栈帧部分从栈里弹出，而协程帧则保留在堆上。当协程首次挂起时，会将一个返回值返回给调用者。这个返回值通常保存了一个指向挂起的协程帧的句柄，稍后可以用于恢复协程。当 x() 被挂起时，它还会把 x() 的恢复点地址储存在协程帧中（称之为 RP）。\n1 2 3 4 5 6 7 8 9 STACK REGISTERS HEAP +----\u0026gt; +-----------+ +------+ | | x() | +----------------+ \u0026lt;----- | rsp | | | a = 42 | | f() | +------+ | | b = 789 | | handle ----|---+ | rbp | | | RP=x()+99 | | ... | | +------+ | +-----------+ | | | | | | +------------------+ This handle may now be passed around as a normal value between functions. At some point later, potentially from a different call-stack or even on a different thread, something (say, h()) will decide to resume execution of that coroutine. For example, when an async I/O operation completes.\n这个句柄现在也许可以作为普通值在函数之间传递。在稍后的某个时间点，可能是不同的调用栈，甚至是在另一个线程上，某个函数（比如 h()）决定恢复该协程的执行。比如，当一个异步 I/O 操作完成时。\nThe function that resumes the coroutine calls a void resume(handle) function to resume execution of the coroutine. To the caller, this looks just like any other normal call to a void-returning function with a single argument.\n协程恢复函数调用 void resume(handle) 来恢复协程的执行。对调用者来说，这就像其他普通的对 void-returning 单参数函数调用一样。\nThis creates a new stack-frame that records the return-address of the caller to resume(), activates the coroutine-frame by loading its address into a register and resumes execution of x() at the resume-point stored in the coroutine-frame.\n这创建了一个新的栈帧，其中记录了 resume() 调用者的返回地址，通过加载它的地址到寄存器中来激活协程帧，并在储存于协程帧的恢复点处，恢复 x() 的执行。\n1 2 3 4 5 6 7 8 9 10 STACK REGISTERS HEAP +----------------+ \u0026lt;-+ | x() | | +--\u0026gt; +-----------+ | ret= h()+0x87 | | +------+ | | x() | +----------------+ +--- | rsp | | | a = 42 | | h() | +------+ | | b = 789 | | handle | | rbp | ------+ +-----------+ +----------------+ +------+ | ... | | | In summary I have described coroutines as being a generalisation of a function that has three additional operations - ‘Suspend’, ‘Resume’ and ‘Destroy’ - in addition to the ‘Call’ and ‘Return’ operations provided by “normal” functions.\n我已经把协程描述为函数的泛化，除了“普通”函数提供的 call 和 Return 操作外，它还有三个额外的操作 - Suspend（挂起），Resume（恢复）和 Destroy（销毁）。\nI hope that this provides some useful mental framing for how to think of coroutines and their control-flow.\n我希望这能提供一些有用的思维框架，帮助你理解协程及其控制流。\nIn the next post I will go through the mechanics of the C++ Coroutines TS language extensions and explain how the compiler translates code that you write into coroutines.\n在下一篇文章中，我将深入讲解 C++ Coroutines TS 语言拓展的机制，并解释编译器是如何将你编写的代码转换为协程的。\n","date":"2025-11-28T08:21:14+08:00","permalink":"https://anfsity.com/en/p/c-coroutines-1/","title":"C++ Coroutines (1)"},{"content":"Useful Resources Lua-guide Neovim Docs Nvim lua guide Build your first Neovim configuration in lua Stack overflow Programming in Lua Lua 5.4 Reference Manual Lua Beginners Guide Also, you can view the documentation by typing :h lua-guide in command mode.\nA Simpler Choice: NvChad I stumbled upon an article titled Environment Configuration Guide/Editor – Neovim Installation \u0026amp; Configuration Tutorial (Based on NvChad) and decided to follow the author\u0026rsquo;s setup.\nThis post serves as a supplement to that article.\nWe use NvChad to simplify our configuration process and add more user-friendly theming features.\nBasic Configuration After pulling the repository, the first thing we need to modify is options.lua.\nOpen ~/.config/nvim/lua/options.lua. The default configuration can be found here: NvChad Options .\nHere is a breakdown of the default configuration:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 local opt = vim.opt local o = vim.o local g = vim.g -------------------------------------- options ------------------------------------------ o.laststatus = 3 o.showmode = false o.splitkeep = \u0026#34;screen\u0026#34; o.clipboard = \u0026#34;unnamedplus\u0026#34; o.cursorline = true o.cursorlineopt = \u0026#34;number\u0026#34; -- Indenting o.expandtab = true o.shiftwidth = 2 o.smartindent = true o.tabstop = 2 o.softtabstop = 2 opt.fillchars = { eob = \u0026#34; \u0026#34; } o.ignorecase = true o.smartcase = true o.mouse = \u0026#34;a\u0026#34; -- Numbers o.number = true o.numberwidth = 2 o.ruler = false -- disable nvim intro opt.shortmess:append \u0026#34;sI\u0026#34; o.signcolumn = \u0026#34;yes\u0026#34; o.splitbelow = true o.splitright = true o.timeoutlen = 400 o.undofile = true -- interval for writing swap file to disk, also used by gitsigns o.updatetime = 250 -- go to previous/next line with h,l,left arrow and right arrow -- when cursor reaches end/beginning of line opt.whichwrap:append \u0026#34;\u0026lt;\u0026gt;[]hl\u0026#34; -- disable some default providers g.loaded_node_provider = 0 g.loaded_python3_provider = 0 g.loaded_perl_provider = 0 g.loaded_ruby_provider = 0 -- add binaries installed by mason.nvim to path local is_windows = vim.fn.has \u0026#34;win32\u0026#34; ~= 0 local sep = is_windows and \u0026#34;\\\\\u0026#34; or \u0026#34;/\u0026#34; local delim = is_windows and \u0026#34;;\u0026#34; or \u0026#34;:\u0026#34; vim.env.PATH = table.concat({ vim.fn.stdpath \u0026#34;data\u0026#34;, \u0026#34;mason\u0026#34;, \u0026#34;bin\u0026#34; }, sep) .. delim .. vim.env.PATH laststatus: The status bar display mode. 0: Never show. 1: Only show if there are at least two windows. 2: Always show. 3: Always show, and it is global (one status bar for all splits). You can visually check the difference by toggling these values.\nshowmode: Literally what it says, displays the current mode. cursorline: Highlights the line where the cursor is currently located. cursorlineopt: line: Highlights the entire line. number: Highlights the line number. both: Highlights both. expandtab: Converts \\t (tabs) to spaces when Tab is pressed. shiftwidth: The width for auto-indenting (or shifting via \u0026gt;\u0026gt; and \u0026lt;\u0026lt;). ignorecase: Ignores case when searching. mouse: Mouse support. a (all) means mouse support is enabled in all modes. There are too many options to explain individually. You can check the documentation or type :h options.\nvim.o options The configuration provided by NvChad is already quite complete. I only made minor modifications to fit my personal habits.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 require \u0026#34;nvchad.options\u0026#34; local o = vim.o local opt = vim.opt -------------------- options --------------------- -- Common o.cursorlineopt =\u0026#39;both\u0026#39; o.list = true opt.listchars = { tab = \u0026#34;» \u0026#34;, trail = \u0026#34;·\u0026#34;, nbsp = \u0026#34;␣\u0026#34; } -- Indenting o.expandtab = false o.shiftwidth = 4 o.showmode = true o.tabstop = 4 o.softtabstop = 4 If you are confused about vim.o, vim.opt, etc., these resources might help:\nDifference between vim.o and vim.opt? Neovim Guide (1): Basic Config To be honest, the official documentation is a good choice, but it\u0026rsquo;s hard to read as a beginner. It acts more like a dictionary than a textbook—better for looking things up than for understanding concepts.\nKey Mappings The feel of key mappings is crucial in coding.\nnvim allows you to customize key bindings using lua.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 require \u0026#34;nvchad.mappings\u0026#34; local map = vim.keymap.set map(\u0026#34;n\u0026#34;, \u0026#34;;\u0026#34;, \u0026#34;:\u0026#34;, { desc = \u0026#34;CMD enter command mode\u0026#34; }) map(\u0026#34;i\u0026#34;, \u0026#34;\u0026lt;A-h\u0026gt;\u0026#34;, \u0026#34;\u0026lt;ESC\u0026gt;^i\u0026#34;, { desc = \u0026#34;move beginning of line\u0026#34; }) map(\u0026#34;i\u0026#34;, \u0026#34;\u0026lt;A-l\u0026gt;\u0026#34;, \u0026#34;\u0026lt;End\u0026gt;\u0026#34;, { desc = \u0026#34;move end of line\u0026#34; }) map(\u0026#34;n\u0026#34;, \u0026#34;F\u0026#34;, \u0026#34;%\u0026#34;, { desc = \u0026#34;jump between match-pair\u0026#34; }) -- use windows like keymaps map({ \u0026#34;n\u0026#34;, \u0026#34;i\u0026#34;, \u0026#34;v\u0026#34; }, \u0026#34;\u0026lt;C-s\u0026gt;\u0026#34;, \u0026#34;\u0026lt;cmd\u0026gt; w \u0026lt;cr\u0026gt;\u0026#34;, { desc = \u0026#34;file save\u0026#34; }) map({ \u0026#34;n\u0026#34; }, \u0026#34;\u0026lt;C-a\u0026gt;\u0026#34;, \u0026#34;ggVG\u0026#34;, { desc = \u0026#34;select all file\u0026#34; }) map({ \u0026#34;n\u0026#34;, \u0026#34;i\u0026#34;, \u0026#34;v\u0026#34; }, \u0026#34;\u0026lt;C-z\u0026gt;\u0026#34;, \u0026#34;\u0026lt;cmd\u0026gt; undo \u0026lt;cr\u0026gt;\u0026#34;, { desc = \u0026#34;history undo\u0026#34; }) map({ \u0026#34;n\u0026#34;, \u0026#34;i\u0026#34;, \u0026#34;v\u0026#34; }, \u0026#34;\u0026lt;C-y\u0026gt;\u0026#34;, \u0026#34;\u0026lt;cmd\u0026gt; redo \u0026lt;cr\u0026gt;\u0026#34;, { desc = \u0026#34;history redo\u0026#34; }) map(\u0026#34;n\u0026#34;, \u0026#34;\u0026lt;C-/\u0026gt;\u0026#34;, \u0026#34;gcc\u0026#34;, { desc = \u0026#34;comment toggle\u0026#34;, remap = true }) map(\u0026#34;i\u0026#34;, \u0026#34;\u0026lt;C-/\u0026gt;\u0026#34;, \u0026#34;\u0026lt;Esc\u0026gt;gcc^i\u0026#34;, { desc = \u0026#34;comment toggle\u0026#34;, remap = true }) -- visual studio code like keymaps map(\u0026#34;n\u0026#34;, \u0026#34;gb\u0026#34;, \u0026#34;\u0026lt;C-o\u0026gt;\u0026#34;, { desc = \u0026#34;jump back\u0026#34; }) map(\u0026#34;n\u0026#34;, \u0026#34;gh\u0026#34;, \u0026#34;\u0026lt;cmd\u0026gt; lua vim.lsp.buf.hover() \u0026lt;cr\u0026gt;\u0026#34;, { desc = \u0026#34;LSP hover\u0026#34; })map(\u0026#34;n\u0026#34;, \u0026#34;ge\u0026#34;, \u0026#34;\u0026lt;cmd\u0026gt; lua vim.diagnostic.open_float() \u0026lt;cr\u0026gt;\u0026#34;, { desc = \u0026#34;LSP show diagnostics\u0026#34; }) map(\u0026#34;n\u0026#34;, \u0026#34;ge\u0026#34;, \u0026#34;\u0026lt;cmd\u0026gt; lua vim.diagnostic.open_float() \u0026lt;cr\u0026gt;\u0026#34;, { desc = \u0026#34;LSP show diagnostics\u0026#34; }) map({ \u0026#34;n\u0026#34;, \u0026#34;i\u0026#34; }, \u0026#34;\u0026lt;A-j\u0026gt;\u0026#34;, \u0026#34;\u0026lt;cmd\u0026gt; :m +1 \u0026lt;cr\u0026gt;\u0026#34;, { desc = \u0026#34;move one line down \u0026#34;}) map({ \u0026#34;n\u0026#34;, \u0026#34;i\u0026#34; }, \u0026#34;\u0026lt;A-k\u0026gt;\u0026#34;, \u0026#34;\u0026lt;cmd\u0026gt; :m -2 \u0026lt;cr\u0026gt;\u0026#34;, { desc = \u0026#34;move one line up \u0026#34;}) map(\u0026#34;v\u0026#34;, \u0026#34;\u0026lt;A-j\u0026gt;\u0026#34;, \u0026#34;:m \u0026#39;\u0026gt;+1\u0026lt;CR\u0026gt;gv=gv\u0026#34;, { desc = \u0026#34;Move selected lines down\u0026#34; }) map(\u0026#34;v\u0026#34;, \u0026#34;\u0026lt;A-k\u0026gt;\u0026#34;, \u0026#34;:m \u0026#39;\u0026lt;-2\u0026lt;CR\u0026gt;gv=gv\u0026#34;, { desc = \u0026#34;Move selected lines up\u0026#34; }) This is my personal key mapping table. Of course, this includes the default mappings provided by NvChad.\nThe terminal key logic implemented by NvChad is very comfortable to use.\nLet\u0026rsquo;s break down some common lua syntax used here.\n\u0026lt;C\u0026gt; represents Ctrl, \u0026lt;A\u0026gt; represents Alt, and the default \u0026lt;Leader\u0026gt; key is Space.\nremap is used as a recursive flag. If mapping A points to B, and I want to create a new mapping C that points to A to achieve the effect of B, I need to tell the map function that I want to create such a continuous mapping. In the code above, gcc is already a mapping itself, so we need to use remap.\nSince I use Linux as my daily driver, I cannot guarantee that this configuration works equally well on Windows.\nPlugins NvChad is managed via LazyNvim. Note that sometimes lazy loading can cause asynchronous issues, so I don\u0026rsquo;t recommend lazy loading for frequently used features.\nRegarding the logic of lazy.nvim, please refer to this article :\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 -- Custom lazy.nvim install path local lazypath = vim.fn.stdpath \u0026#34;data\u0026#34; .. \u0026#34;/lazy/lazy.nvim\u0026#34; -- If lazy.nvim doesn\u0026#39;t exist, clone it from Git to the specified path if not vim.uv.fs_stat(lazypath) then local repo = \u0026#34;https://github.com/folke/lazy.nvim.git\u0026#34; vim.fn.system { \u0026#34;git\u0026#34;, \u0026#34;clone\u0026#34;, \u0026#34;--filter=blob:none\u0026#34;, repo, \u0026#34;--branch=stable\u0026#34;, lazypath } end -- Add lazy.nvim\u0026#39;s install path to Neovim\u0026#39;s runtime path so Neovim can find it vim.opt.rtp:prepend(lazypath) -- The file required here is `lua/configs/lazy.lua`, which contains basic config for lazy.nvim local lazy_config = require \u0026#34;configs.lazy\u0026#34; -- Load plugins via lazy.nvim -- lazy.nvim will automatically download and load plugins specified in `.setup` require(\u0026#34;lazy\u0026#34;).setup({ -- Load NvChad first { \u0026#34;NvChad/NvChad\u0026#34;, lazy = false, branch = \u0026#34;v2.5\u0026#34;, import = \u0026#34;nvchad.plugins\u0026#34;, }, -- Then look for and load plugins from the `plugins/` directory -- (i.e., `lua/plugins/` in your current config folder) { import = \u0026#34;plugins\u0026#34; }, }, lazy_config) Logic referenced from Environment Configuration Guide/Editor – Neovim Installation \u0026amp; Configuration Tutorial (Based on NvChad) .\nHere, require(\u0026quot;lazy\u0026quot;).setup() requires a table as a return value to accept the configuration.\nSimilarly, inside the plugins folder, it doesn\u0026rsquo;t have to be a single init.lua; it can be multiple *.lua files.\nLet\u0026rsquo;s look at the general format for installing plugins with lazy.nvim:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 return { -- the colorscheme should be available when starting Neovim { \u0026#34;folke/tokyonight.nvim\u0026#34;, lazy = false, -- make sure we load this during startup if it is your main colorscheme priority = 1000, -- make sure to load this before all the other start plugins config = function() -- load the colorscheme here vim.cmd([[colorscheme tokyonight]]) end, }, -- I have a separate config.mappings file where I require which-key. -- With lazy the plugin will be automatically loaded when it is required somewhere { \u0026#34;folke/which-key.nvim\u0026#34;, lazy = true }, { \u0026#34;nvim-neorg/neorg\u0026#34;, -- lazy-load on filetype ft = \u0026#34;norg\u0026#34;, -- options for neorg. This will automatically call `require(\u0026#34;neorg\u0026#34;).setup(opts)` opts = { load = { [\u0026#34;core.defaults\u0026#34;] = {}, }, }, }, -- ... (omitted similar examples for brevity) Config from lazy.nvim examples It basically returns an array containing multiple Plugin Specs . You noticed that the length of each array varies; lazy.nvim supports returning a single Spec or an array containing multiple Specs, allowing you to organize your plugin configuration flexibly.\n\u0026quot;folke/tokyonight.nvim\u0026quot; represents the github repository name, allowing lazy.nvim to automatically pull code from github.\nlazy = false indicates whether to enable lazy loading. false means it is disabled (load immediately). Note that there is also an event directive, which also implies lazy loading, but enables the plugin when the specific event occurs.\nopts and config are passed to the plugin\u0026rsquo;s setup() function as a Lua table or a function returning a Lua table. Due to logic issues, I recommend using opts instead of config in scenarios like this, although they are equivalent:\n1 2 3 4 5 6 return { \u0026#34;stevearc/conform.nvim\u0026#34;, config = function() require(\u0026#34;conform\u0026#34;).setup({}) end } 1 2 3 4 return { \u0026#34;stevearc/conform.nvim\u0026#34;, opts = {} } config supports more complex logic. That is, when logical operations are needed, we use config; when just declaring configuration and describing requirements, we should use opts. In most use cases, we should use opts.\nIt\u0026rsquo;s worth noting that if you want to install a plugin that is a native vim plugin, we need to call the init method.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 return { { \u0026#34;mg979/vim-visual-multi\u0026#34;, lazy = false, init = function () vim.g.VM_maps = { [\u0026#34;Find Under\u0026#34;] = \u0026#39;\u0026lt;C-f\u0026gt;\u0026#39;, } vim.g.VM_maps_disable = { [\u0026#34;i\u0026#34;] = \u0026#34;A\u0026#34;, } end, }, } The dependencies option describes the dependencies required by the repository, facilitating lazy.nvim to pull and maintain them.\nNext, we need to prepare for the LSP service and organize the framework under the nvim folder to suit our personal habits:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 ├── lua │ ├── autocmds.lua │ ├── chadrc.lua │ ├── configs │ │ ├── conform.lua │ │ ├── highlight.lua │ │ ├── lazy.lua │ │ ├── lsp.lua │ │ ├── luasnip.lua │ │ └── ui.lua │ ├── lua_snippets │ │ └── snippets.lua │ ├── mappings.lua │ ├── options.lua │ └── plugins │ ├── comments.lua │ ├── conform.lua │ ├── highlight.lua │ ├── lsp.lua │ ├── luasnip.lua │ ├── motions.lua │ ├── tools.lua │ └── ui.lua If you need further introduction to plugins, you can check the Zhihu article mentioned earlier. Now, let\u0026rsquo;s take a big step towards our goal \u0026ndash; configuring LSP.\nBtw, regarding snippets which was not mentioned in that article: this is a very common feature in IDEs. In Neovim, we use the luasnip plugin to get this functionality.\nluasnip provides several related APIs. luasnip.s provides the interface for the snippet. I use two interfaces: luasnip.extras.fmt (a formatting tool provided by luasnip) and luansip.t (which uses the vscode snippet format). So, you can migrate from vscode completely painlessly.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 local luaSnip = require(\u0026#34;luasnip\u0026#34;) -- snippet local s = luaSnip.s -- insert node local i = luaSnip.i -- text node local t = luaSnip.t -- formatter tool local fmt = require(\u0026#34;luasnip.extras.fmt\u0026#34;).fmt luaSnip.add_snippets(\u0026#34;cpp\u0026#34;, { s( { trig = \u0026#34;demo\u0026#34;, name = \u0026#34;Competitive Programming Template\u0026#34;, dscr = \u0026#34;A template for competitive programming. \u0026#34;}, t({ -- some vsocde like snippets here }) ) } Finally, you need to turn off lazyload for this plugin. Lazy.nvim has some bugs that cause plugins to fail to enable correctly. I haven\u0026rsquo;t investigated the details, but I suspect it\u0026rsquo;s an async issue caused by lazy loading.\nLSP (Language Server Protocol) I have to admit, LSP might be the most complex part of configuring Neovim. This is perhaps the most crucial part of this article; only when it\u0026rsquo;s configured can you enjoy full code completion on Neovim — but first, what is LSP?\nSimply put, the LSP protocol consists of two core components: the Language Client and the Language Server. As the name suggests, the Language Client is responsible for rendering the user interface (highlighting, hover hints), monitoring user actions, and converting them into language-specific requests to forward to the Language Server. The Language Server is responsible for receiving information from the Language Client, processing it, and sending the results (code completion, error messages, etc.) back to the Language Client.\nThe protocol separates the \u0026ldquo;frontend\u0026rdquo; and \u0026ldquo;backend\u0026rdquo; logic of full language support. From then on, Editors/IDEs only need to implement the Language Client, while language developers and maintainers only need to implement the Language Server. This greatly reduces the workload for developers and improves the user experience.\nHistorically, each Editor/IDE was responsible for implementing features for corresponding languages. This led to different implementation methods for supporting the same language across editors, varying degrees of support, and developers having to maintain implementations for multiple platforms, doing a lot of repetitive work. It also led to vast differences in experience across different editors. To solve this, Microsoft proposed the LSP protocol.\nThis is a very rough understanding of LSP, but it\u0026rsquo;s not the main point of this article. If you are interested in the LSP protocol, you might want to try reading Microsoft\u0026rsquo;s documentation .\nNeovim has built-in interfaces for language servers since version 0.5+. You can use Neovim\u0026rsquo;s own interfaces to implement a fully functional LSP client, but we don\u0026rsquo;t need to reinvent the wheel (although doing so for a single language isn\u0026rsquo;t complex, it gets increasingly complicated as you maintain more languages, and it\u0026rsquo;s not easy to migrate or backup) — others have already paved the way. A plugin called nvim-lspconfig contains LSP configurations for many mainstream languages. Just load this plugin, and these configurations will automatically load into Neovim. And all you need is a simple vim.lsp.enable(...).\ngraph TD subgraph \u0026#34;📦 Package Management \u0026amp; Installation\u0026#34; A[nvim-mason] --\u0026gt;|Installs| B[Language Servers, e.g., clangd, rust_analyzer]; A --\u0026gt;|Installs| C[Formatters \u0026amp; Linters, e.g., prettier, stylua]; end subgraph \u0026#34;🔌 Core Configuration \u0026amp; Integration\u0026#34; D[nvim-lspconfig] --\u0026gt;|Reads User Config| B; D --\u0026gt;|Configures \u0026amp; Attaches| E[Neovim\u0026#39;s Built-in LSP Client]; B --\u0026gt;|Communicates via LSP Protocol| E; end subgraph \u0026#34;🧠 Enhanced Syntax \u0026amp; Parsing\u0026#34; F[nvim-treesitter] --\u0026gt;|Provides Rich Syntax Trees| E; F --\u0026gt;|Improves| G[Syntax Highlighting]; F --\u0026gt;|Enables| H[\u0026#34;Text Objects, e.g., [a] for argument\u0026#34;]; end subgraph \u0026#34;✨ User Experience \u0026amp; UI\u0026#34; E --\u0026gt;|Provides Completion Data| I[nvim-cmp]; J[luasnip, etc.] --\u0026gt;|Provides Snippet Data| I; I --\u0026gt;|Renders UI| K[Autocomplete Popup Menu]; E --\u0026gt;|Provides Signature Help| L[Signature Help Popup]; I --\u0026gt;|Triggers Auto-Pairing| M[nvim-autopairs]; K -- Triggers --\u0026gt; M; end subgraph \u0026#34;Legend\u0026#34; subgraph \u0026#34;Plugins\u0026#34; A; D; F; I; J; M; end subgraph \u0026#34;LSP Servers / Tools\u0026#34; B; C; end subgraph \u0026#34;Neovim Core\u0026#34; E; end subgraph \u0026#34;User Features\u0026#34; G; H; K; L; end end %% Styling style A fill:#f9f,stroke:#333,stroke-width:2px style B fill:#bbf,stroke:#333,stroke-width:2px style E fill:#9f9,stroke:#333,stroke-width:2px style F fill:#fcf,stroke:#333,stroke-width:2px style I fill:#ff9,stroke:#333,stroke-width:2px The diagram above illustrates the communication between Neovim\u0026rsquo;s built-in LSP Client and its plugins. Common plugins include lsp-config for simplified configuration, nvim-autopairs and nvim-cmp for code completion, Mason as the package manager, and nvim-treesitter responsible for more complete code highlighting and error UI.\nConfiguring lsp-config is not complicated. Below, we\u0026rsquo;ll use lua_ls as an example to explain the entire configuration flow.\nFirst, it should be mentioned that while lsp-config helps you configure LSP, it doesn\u0026rsquo;t install them for you. We use the Mason plugin to automatically install the required LSP, DAP, linters, etc. Use :Mason to call up the Mason panel, g? to view related shortcuts, and / to search. The usage is simple and the hints are comprehensive, so I won\u0026rsquo;t go into detail here.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 local mr = require \u0026#34;mason-registry\u0026#34; local nvlsp = require \u0026#34;nvchad.configs.lspconfig\u0026#34; local eagerly_installed_langs = { ... } --- A pre-install list local ensure_installed = { [\u0026#34;*\u0026#34;] = { \u0026#34;typos_lsp\u0026#34; }, --- typos_lsp is a language server for spell checking Bash = { \u0026#34;bashls\u0026#34;, \u0026#34;shellcheck\u0026#34;, \u0026#34;shfmt\u0026#34; }, C = { \u0026#34;clangd\u0026#34;, \u0026#34;clang-format\u0026#34; }, ... } --- List of mandatory installations --- vim.api.nvim_create_autocmd creates an autocommand --- An autocommand executes a callback function automatically when a specific event occurs --- LspAttach is the event we are listening for, triggered when a language server successfully attaches to a buffer vim.api.nvim_create_autocmd(\u0026#34;LspAttach\u0026#34;, { callback = function(args) nvlsp.on_attach(_, args.buf) --- on_attach function implemented by NvChad --- This function is usually responsible for LSP-related shortcuts in this buffer --- _ in Lua represents a discarded variable end, }) --- vim.lsp.config is a core config function of nvim-lspconfig plugin vim.lsp.config(\u0026#34;*\u0026#34;, { --- on_attach is a callback function provided by nvim-lspconfig --- client is the language server communicating with the buffer, bufnr is the current buffer number on_attach = function(client, bufnr) if client.supports_method(\u0026#34;textDocument/inlayHint\u0026#34;) or client.server_capabilities.inlayHintProvider then vim.lsp.inlay_hint.enable(true, { bufnr = bufnr }) end --- If the current language server supports inlayHint, we enable it --- Similar functionality if client.supports_method(\u0026#34;textDocument/codeLens\u0026#34;, { bufnr = bufnr }) then vim.lsp.codelens.refresh { bufnr = bufnr } vim.api.nvim_create_autocmd({ \u0026#34;BufEnter\u0026#34;, \u0026#34;InsertLeave\u0026#34; }, { --- Listen to two events: BufEnter (enter buffer), InsertLeave (leave insert mode) buffer = bufnr, --- This command only applies to the current buffer callback = function() vim.lsp.codelens.refresh { bufnr = bufnr } end, }) end end, on_init = nvlsp.on_init, capabilities = nvlsp.capabilities, }) --- Start: LuaLS config --- dofile(vim.g.base46_cache .. \u0026#34;lsp\u0026#34;) --- dofile is a built-in Lua function to execute a Lua file directly --- vim.g.base56_cache is the path set by NvChad require(\u0026#34;nvchad.lsp\u0026#34;).diagnostic_config() --- This is also the diagnostic style implemented by NvChad local lua_ls_settings = { Lua = { hint = { enable = true, paramName = \u0026#34;Literal\u0026#34;, }, --- Enable inlay hints, a feature I really like --- Literal: only show parameter names when the function argument is a literal codeLens = { enable = true, }, --- Show the reference count of the function workspace = { maxPreload = 1000000, --- Set the max total size for preloading and analysis by lua_ls on startup, in bytes preloadFileSize = 10000, --- Set the max size of a single file for preloading by lua_ls, in bytes }, }, } -- If current working directory is Neovim config directory local in_neovim_config_dir = (function() local stdpath_config = vim.fn.stdpath \u0026#34;config\u0026#34; --- Anonymous function, getting nvim\u0026#39;s config directory path local config_dirs = type(stdpath_config) == \u0026#34;string\u0026#34; and { stdpath_config } or stdpath_config --- Ensure config_dirs is always a list ---@diagnostic disable-next-line: param-type-mismatch for _, dir in ipairs(config_dirs) do if vim.fn.getcwd():find(dir, 1, true) then return true end end end)() --- The following configuration is only enabled in the nvim config directory --- It will not affect regular Lua projects if in_neovim_config_dir then -- Add vim to globals for type hinting lua_ls_settings.Lua.diagnostic = lua_ls_settings.Lua.diagnostic or {} lua_ls_settings.Lua.diagnostic.globals = lua_ls_settings.Lua.diagnostic.globals or {} table.insert(lua_ls_settings.Lua.diagnostic.globals, \u0026#34;vim\u0026#34;) -- Add all plugins installed with lazy.nvim to `workspace.library` for type hinting lua_ls_settings.Lua.workspace.library = vim.list_extend({ vim.fn.expand \u0026#34;$VIMRUNTIME/lua\u0026#34;, vim.fn.expand \u0026#34;$VIMRUNTIME/lua/vim/lsp\u0026#34;, \u0026#34;${3rd}/busted/library\u0026#34;, -- Unit testing \u0026#34;${3rd}/luassert/library\u0026#34;, -- Unit testing \u0026#34;${3rd}/luv/library\u0026#34;, -- libuv bindings (`vim.uv`) }, vim.fn.glob(vim.fn.stdpath \u0026#34;data\u0026#34; .. \u0026#34;/lazy/*\u0026#34;, true, true)) --- The purpose of the code above is to automatically include the source code directories of all plugins --- into the completion scope of lua_ls. end vim.lsp.config(\u0026#34;lua_ls\u0026#34;, { settings = lua_ls_settings, }) This logic might seem a bit complex, but apart from a function determining if it\u0026rsquo;s in the nvim config directory and the corresponding handling, the rest of the logic is quite simple.\nThe author of the Zhihu article also used mason-lspconfig to automate the installation of corresponding language servers. I don\u0026rsquo;t have that need, and the code is quite long, so I didn\u0026rsquo;t look at it closely.\nMastering these is enough to configure features specific to other language servers. A quick note on clangd: the inlayHint check above won\u0026rsquo;t be triggered by clangd. If you want to use the inlayHint provided by clangd, you can hardcode it like I did, since we know it supports this feature anyway.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 vim.lsp.config(\u0026#34;clangd\u0026#34;, { on_attach = function (_, bufnr) vim.lsp.inlay_hint.enable(true, { bufnr = bufnr }) end, single_file_support = true, cmd = { \u0026#34;clangd\u0026#34;, \u0026#34;--clang-tidy\u0026#34;, \u0026#34;--j=12\u0026#34;, \u0026#34;--background-index\u0026#34;, \u0026#34;--header-insertion=never\u0026#34;, \u0026#34;--inlay-hints=true\u0026#34;, \u0026#34;--fallback-style=LLVM, indent=4\u0026#34;, } }) That\u0026rsquo;s about it for the main body of LSP. It\u0026rsquo;s not actually complicated, but it\u0026rsquo;s easy to get confused when you first start.\nBy now, you should have a performant editor. Neovim also supports many extensions, such as formatting, Copilot, background beautification, etc. You can check that Zhihu article for these. Basically, any feature you can experience on VSCode, Neovim can achieve, and faster—the downside is that configuration is more troublesome. But once configured, you can pull your config from GitHub anytime, anywhere. Plus, isn\u0026rsquo;t it fun to craft a custom editor with your own hands?\nConclusion Actually, this post is more like a supplement to details and omissions in that Zhihu article. Writing it also helped deepen my familiarity with this tool. Finally, here is the link to my personal repository .\n","date":"2025-09-23T01:31:05+08:00","permalink":"https://anfsity.com/en/p/neovim/","title":"Neovim"},{"content":"A sudden burst of inspiration, serving as a summary of my academic year.\nFirst, let me quote a passage from the Survival Manual for SJTU:\nDear students, at the beginning of this book, I have the unfortunate task of informing you of a piece of news. Undergraduate teaching in the vast majority of domestic universities is not on the verge of collapse; it has already collapsed. Here, I have no intention of arguing whether Fudan, USTC, Tsinghua, or Peking University have collapsed slightly less than we have—such a debate is meaningless. I simply see countless students, full of curiosity, passion, and youthful dreams, who are about to hand over four years of their youth to the university to be shaped, filled with hope and trust. This makes me feel very uneasy.\nIn fact, I feel very fortunate. My luck has been decent, and my information retrieval skills are acceptable. Right after the Gaokao (National College Entrance Exam) ended, I came across the Survival Manual for SJTU . Not long after I began studying CS, I discovered csdiy . Although I am not at my dream university (having underperformed in the Gaokao), it is at least the major I chose for myself—a major I have enough passion and interest in to fully commit to.\nWhen I first entered university, I asked some outstanding seniors about their reasons for choosing the CS major. The answers I received, however, were often pragmatic considerations such as \u0026ldquo;this major has potential\u0026rdquo; or \u0026ldquo;it makes good money.\u0026rdquo; I couldn\u0026rsquo;t help but wonder how many people truly harbor clear goals and dreams during these precious four years. What do they fervently pursue? What achievements do they long to reach? What kind of future will they shape? Yet, as I type these words, the sounds reaching my ears are still the clamor of my roommates immersed in video games. I do not claim to be sober or superior; I simply feel that wasting youth in such a manner is a profound pity and truly regrettable.\nFor someone like me who has loved \u0026ldquo;tinkering\u0026rdquo; since childhood, it might not be surprising that I\u0026rsquo;m interested in CS. Unfortunately, due to equipment limitations, my understanding of CS was actually very, very superficial. I didn\u0026rsquo;t understand the command line, I didn\u0026rsquo;t understand programming languages, and I didn\u0026rsquo;t understand networking. Essentially, I was a complete novice who knew nothing. Perhaps using a few plugins, knowing a few software titles, or messing around with the Windows system—these simple things even to make me believe I understood computers very well. Looking back now, it is quite embarrassing. Regardless, to figure these things out, I learned how to search and got used to checking documentation, which was a good start after all.\nThe Gaokao is, after all, a periodic selection method. No selection method can be all-encompassing enough to help universities admit exactly the students they want most. Since almost all primary and secondary education serves the Gaokao selection process (excluding those attending undergraduate programs abroad), many students have subconsciously formed a linear thinking mode. In university, there will be GPA evaluation standards, but the university no longer relies on grades as the sole dimension to linearly judge a person\u0026rsquo;s excellence, as the Gaokao does. Upon graduation, your development will have little to do with your initial Gaokao score. In those four years, countless opportunities will await your grasp, and they will greatly influence the direction of your future life. After entering society, you will find that although statistically the better the university, the higher the achievements, the variance in the paths of graduates from any single school will be larger than you can imagine.\nHowever, what is even more disheartening is that the number of classmates around me willing to put in the effort to \u0026ldquo;grind\u0026rdquo; is already small. Among this minority, those who can avoid being held hostage by GPA and truly work for the sake of knowledge itself are few and far between. As the author said, undergraduate education in universities has already collapsed. Based on my personal experience with the courses offered by my school, I can say that only the C Programming course can be considered somewhat useful. Even so, it is far from enough to serve as a cornerstone for a career in the industry. As for the remaining courses, you don\u0026rsquo;t even know the point of them being offered.\nIn our school, C language has a proper OJ \u0026hellip;\n\u0026ldquo;What do you want to do?\u0026rdquo; — This question has frequently lingered in my mind since the Gaokao. When I first enrolled, I was very lost. The expectations of my family and the disorientation of a new environment caused me to lose my direction. During the winter break, I finally figured it out: you cannot \u0026ldquo;grasp all and win all.\u0026rdquo; Obsessing over GPA was never my intention, nor was indulging in comfort and wasting time. To the point that I feel some regret—why didn\u0026rsquo;t I start the path of self-study earlier? Why did I still follow the school\u0026rsquo;s curriculum step-by-step? Some matters are still dragging down my pace even now.\nWhatever we do, we need to give ourselves a reason. Being busy every day without any original ideas, forced by the pressures of life, can be called one of life\u0026rsquo;s great tragedies.\nWhen stepping through the university gates, the biggest question we face is: Why attend class? Perhaps because the question itself is too obvious, we are even too lazy to think about it. But who among us has truly and effectively thought about this question?\n\u0026ldquo;Fearing the teacher\u0026rsquo;s roll call,\u0026rdquo; \u0026ldquo;to copy notes and homework,\u0026rdquo; \u0026ldquo;to record exam highlights\u0026rdquo;\u0026hellip; these statements, at best, are excuses for being forced to attend class, but they cannot be the reasons we attend class out of conviction.\nThe only thing that can truly become a reason for us to attend class is our thirst for scientific and cultural knowledge.\nIf whether you attend class has little impact on your exam results; if the knowledge we are interested in is not on the school\u0026rsquo;s timetable; if the effect of learning in class is poor enough and the efficiency low enough that through self-study, you can master the knowledge in a shorter time; then do you still need to go to class?\nDespite the current situation, I still see people online whose views align with mine, meet many admired seniors, and find predecessors who have distilled their journeys into experience, building platforms for us to learn from.\nPlease remember, there are always things more worth doing. Please keep your sights on the long term. Do not attend class for the sake of GPA. Think independently about whether attending class, studying, and exams are truly worth doing, and whether day after day of exercise problems is truly necessary to execute. The reason we refuse to learn knowledge that is not particularly useful to us is that the value of that knowledge to us is too low.\nNever expect the school to personally arrange a broad and smooth road for you. The true path must ultimately be carved out by yourself, one step at a time.\nLearning is not necessarily painful, but learning without pain, or learning without the feeling of joy, yields no gain.\nFinally, I will quote a passage from csdiy as a closing remark:\nYou must have enough drive to force yourself to calm down, read dozens of pages of Project Handouts, understand code frameworks with thousands of lines, and endure hours of debugging time. And all of this comes with no credits, no GPA, no teachers, and no classmates—only one belief: you are becoming stronger.\nP.S. The English text above was translated by a Large Language Model without manual proofreading. Please excuse any unnatural phrasing or slight losses in the original emotional nuance.\n","date":"2025-05-24T22:31:23+08:00","permalink":"https://anfsity.com/en/p/essay-no.-1/","title":"Essay No. 1"}]