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:...

2023.08.27 · 373 字 · YuAng Chen

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....

2023.08.25 · 154 字 · YuAng Chen

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....

2023.08.22 · 330 字 · YuAng Chen

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; }```

2023.08.17 · 103 字 · YuAng Chen

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....

2023.05.13 · 198 字 · 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)...

2023.05.07 · 80 字 · YuAng Chen

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 }...

2023.05.02 · 238 字 · YuAng Chen

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....

2023.05.02 · 158 字 · Yac

Vector vs Array

Another post recycled from my earlier notes. I really don’t have motivation to improve it further 🦥. Vector vs Array Initilization The Vector is the preferred choice for data storage in mordern C++. It is internally implemented based on the Array. However, the performance gap between the two is indeed obvious. The Vector can be initialized via std::vector<T> vec(size). Meanwhile, an Array is initialized by T* arr = new T[size]...

2023.05.01 · 460 字 · Yac

Gather with SIMD

Writing SIMD code that works across different platforms can be a challenging task. The following log illustrates how a seemingly simple operation in C++ can quickly escalate into a significant problem. Let’s look into the code below, where the elements of x is accessed through indices specified by idx. normal code std::vector<float> x = /*some data*/ std::vector<int> idx = /* index */ for(auto i: idx) { auto data = x[i]; } Gather with Intel In AVX512, Gather is a specific intrinsic function to transfer data from a data array to a target vec, according to an index vec....

2023.04.27 · 1014 字 · Yac