STL Containers

In my HPC-oriented programming, my go-to choices are typically limited to arrays and vectors because of their memory efficiency. Linked lists and hash maps, being non-contiguous in memory space, rarely find their way into my toolkit. These containers draw upon many classic algorithmic designs. Lately, as I’ve been revisiting fundamental graph algorithms, I’ve also decided to take on the tasks of re-implementing these containers in a simplified illustration. They are:...

August 30, 2023 · 310 words · Me

Scope Guard

Background Scope Guard is a concept reminiscent of the RAII (Resource Acquisition Is Initialization) principle in C++. The idea is to manage resources (like memory, files, network sockets, etc.) using object lifetime. When the object goes out of scope, its destructor ensures that the resource is cleaned up properly. The scope guard is intended to run a given callable (like a function or lambda) when it is destroyed. RAII (Resource Acquisition Is Initialization) is a programming idiom used in C++ where the lifetime of an object is bound to the lifetime of its scope (typically represented by a block of code wrapped in curly braces {})....

August 29, 2023 · 629 words · Me

Static Local Member

C++ templates are blueprints and don’t represent specific types until they are instantiated with actual types. Once instantiated, the compiler creates a specific version of that template for the provided type. For template classes, each instantiation has its own unique version of the static members, making them distinct for each type the template is instantiated with. ///////////////////// // Code Block 1 ///////////////////// #include<iostream> class ComponentBase{ protected: // component_type_count is a static variable shared by derived classes static inline size_t component_type_count = 0; }; template<typename T> class Component : public ComponentBase{ public: static size_t component_type_id(){ // ID is the static local variable for a particular type T static size_t ID = component_type_count++; return ID; } }; class A : public Component<A> {}; class B : public Component<B> {}; class C : public Component<C> {}; int main() { std::cout << A::component_type_id() << std::endl; // 0 std::cout << B::component_type_id() << std::endl; // 1 std::cout << B::component_type_id() << std::endl; // 1 std::cout << A::component_type_id() << std::endl; // 0 std::cout << A::component_type_id() << std::endl; // 0 std::cout << C::component_type_id() << std::endl; // 2 } Key Points:...

August 27, 2023 · 373 words · Me

Formatter Specialization

We can customize the (printing) format of a given class by using the specialization of formatter. #include <format> #include <iostream> struct Frac { int a, b; }; template <> struct std::formatter<Frac> : std::formatter<string_view> { // parse() is inherited from the base class std::formatter<string_view> // * an efficient solution: auto format(const Frac& frac, std::format_context& ctx) const { return std::format_to(ctx.out(), "{}/{}", frac.a, frac.b); } // the same functionality as above, but inefficient due to the temporary string // auto format(const Frac& frac, std::format_context& ctx) const { // std::string temp; // std::format_to(std::back_inserter(temp), "{}/{}", // frac....

August 25, 2023 · 154 words · Me

User Defined Literals

User Defined Literals (UDL) produces an object in an interesting way: constexpr auto operator""_f(const char* fmt, size_t) { return[=]<typename... T>(T&&... Args) { return std::vformat(fmt, std::make_format_args(std::forward<T>(Args)...)); }; } auto s = "example {} see {}"_f("yep", 1.1); // s = "example yep 1.1" The UDL _f has the same effect of std::format("example {} see {}", "yep", 1.1). Pretty familiar (as libfmt), right? Now, let’s break the definition of _f down: int x = 10; double y = 3....

August 22, 2023 · 330 words · Me

Operator Overload

Reference: here. The return of overloaded operator should be a reference, otherwise return-by-code will create a (temporary) rvalue that cannot be passed to the next operation f2 by non-const reference. i.e., rvalue cannot be non-const referenced. #include <vector> #include <iostream> #include <functional> template<typename T, typename FN> requires std::invocable<FN, T&> // diff std::invocable? std::vector<T>& operator| (std::vector<T>& vec, FN fn) noexcept { for(auto& e: vec) { fn(e); } return vec; } int main(){ std::vector v{1, 2, 3}; auto f1 = [](int& i) {i *= i; }; std::function f2 {[](const int& i) {std::cout << i << ' '; } }; v | f1 | f2; }```

August 17, 2023 · 103 words · Me

Multidimensional Subscript Operator []

Finally, C++23 allows overload for the subscript operator [] to be multi-dimensional. Before that, we normally either use: vector of vector to form a matrix, and access it as mat[i][j] a class containing a big 1-d vector, but behaves as 2-d by overloading the operator (), e.g., mat(i,j) Now, with C++23, we advance the second option (which offers efficient memory access) with better indexing approaching as follow: template <typename T, size_t R, size_t C> struct matrix { T& operator[](size_t const r, size_t const c) noexcept { return data_[r * C + c]; } T const& operator[](size_t const r, size_t const c) const noexcept { return data_[r * C + c]; } static constexpr size_t Rows = R; static constexpr size_t Columns = C; private: std::array<T, R * C> data_; }; int main() { matrix<int, 3, 2> m; for(size_t i = 0; i < m....

May 13, 2023 · 198 words · Yac

Bitwise Op

🦥 An old note. Bitwise vs Arithmetic running on a vector of size 2^31, bitwise operations are significantly faster than arithmetic counterparts: seg = 64; volume = (vec_size - 1)/ seg + 1; unsigned bs = log2(seg); unsigned bv= log2(volume); unsigned bbv = volume - 1; Arithmetic: out[i] = i % volume * seg + i / volume Bitwise: out[i] = ((i & bbv) << bs) + (i >> bv)...

May 7, 2023 · 80 words · Me

Omp Parallel Region

The results look suspicious to me… But I wrote down this note many days ago 🦥. Maybe I need to evaluate it again. Multiple Parallel Regions The cost of constructing parallel region is expensive in OpenMP. Let’s use two example for illustration: Three loops operating on a vector of size 2^31, e.g., for(size_t i = 0; i < vec.size(); i++) vec[i] += 1, vec[i] *= 0.9, vec[i] /= 7, Case 1: a large parallel region including the three loops by omp parallel { omp for }...

May 2, 2023 · 238 words · Me

Omp Collapse

One of my old-day notes 🦥. Collapse of Nested Loops The collapse clause converts a prefect nested loop into a single loop then parallelize it. The condition of a perfect nested loop is that, the inner loop is tightly included by the outer loop, and no other codes lying between: for(int i = 0 ... ) { for(int j = 0 ...) { task[i][j]; } } Such condition is hard to meet....

May 2, 2023 · 158 words · Yac