techhub.social is one of the many independent Mastodon servers you can use to participate in the fediverse.
A hub primarily for passionate technologists, but everyone is welcome

Administered by:

Server stats:

4.6K
active users

#TuesdayCodingTips

0 posts0 participants0 posts today
Jakub Neruda<p>Tip 89 of <a href="https://techhub.social/tags/TuesdayCodingTips" class="mention hashtag" rel="tag">#<span>TuesdayCodingTips</span></a> - Semantic versioning</p><p>When creating a library, semantic versioning is a really useful versioning scheme to use.</p><p>It&#39;s a promise to your users that changes bumping the minor/patch version numbers are backward-compatible. As such, much of the software relies on these numbers to figure out whether a particular version requirement can be satisfied by higher-version binaries.</p><p>For example, Unix SONAME links provide a stable way to (typically) reference a major version of a library, while the pointed-to file can be updated to newer revisions.</p><p>If you only specify the major (or major.minor) version number, CMake Find* scripts will find the newest compatible package. Various wildcards in cargo, npm, and other package managers follow the same principles.</p><p>Just be sure to read the full FAQ at <a href="http://semver.org" target="_blank" rel="nofollow noopener" translate="no"><span class="invisible">http://</span><span class="">semver.org</span><span class="invisible"></span></a> and know that adhering to this versioning scheme is hard, but your users will love you for it... (1/2)</p><p><a href="https://techhub.social/tags/tips" class="mention hashtag" rel="tag">#<span>tips</span></a> <a href="https://techhub.social/tags/programming" class="mention hashtag" rel="tag">#<span>programming</span></a> <a href="https://techhub.social/tags/semver" class="mention hashtag" rel="tag">#<span>semver</span></a></p>
Jakub Neruda<p>Tip 88 of <a href="https://techhub.social/tags/TuesdayCodingTips" class="mention hashtag" rel="tag">#<span>TuesdayCodingTips</span></a> - Enum-discriminated unions with nlohmann::json</p><p>JSON is a fairly trivial format and as such, its only way of supporting polymorphic data is an enum-discriminated approach.</p><p>In other words, you might have an array of objects where each object has a &#39;type&#39; property and all other properties are determined by the value of the type. Circle might have radius, rectangle has dimensions, both have position.</p><p>But how would you parse that in C++ with `nlohmann::json` and a minimal boilerplate? While the library doesn&#39;t have a native support for `std::variant`, let alone recognizing this pattern, it allows you to extend it easily with a custom `adl_serializer` specialization.</p><p>Remaining objects can still be handled by appropriate macros provided by the library to reduce further boilerplate.</p><p>Compiler explorer link: <a href="https://godbolt.org/z/KsW4dhj5j" target="_blank" rel="nofollow noopener" translate="no"><span class="invisible">https://</span><span class="">godbolt.org/z/KsW4dhj5j</span><span class="invisible"></span></a></p><p><a href="https://techhub.social/tags/cpp" class="mention hashtag" rel="tag">#<span>cpp</span></a> <a href="https://techhub.social/tags/tips" class="mention hashtag" rel="tag">#<span>tips</span></a> <a href="https://techhub.social/tags/programming" class="mention hashtag" rel="tag">#<span>programming</span></a></p>
Jakub Neruda<p>They say learning by example is the best way to learn. I&#39;d like to share one such example of API design process based on a recent discussion with one of my colleagues: <a href="https://medium.com/@nerudaj/a-case-study-on-api-design-f22a8665cf2d" target="_blank" rel="nofollow noopener" translate="no"><span class="invisible">https://</span><span class="ellipsis">medium.com/@nerudaj/a-case-stu</span><span class="invisible">dy-on-api-design-f22a8665cf2d</span></a></p><p>If you follow my <a href="https://techhub.social/tags/TuesdayCodingTips" class="mention hashtag" rel="tag">#<span>TuesdayCodingTips</span></a>, this showcases a practical application of many of them.</p><p>And if you dislike clean code for whatever reason, this article is an example of how to overengineer your code in three simple steps I guess :D</p><p><a href="https://techhub.social/tags/CleanCode" class="mention hashtag" rel="tag">#<span>CleanCode</span></a> <a href="https://techhub.social/tags/SoftwareArchitecture" class="mention hashtag" rel="tag">#<span>SoftwareArchitecture</span></a> <a href="https://techhub.social/tags/SoftwareEngineering" class="mention hashtag" rel="tag">#<span>SoftwareEngineering</span></a></p>
Jakub Neruda<p>Tip 87 of <a href="https://techhub.social/tags/TuesdayCodingTips" class="mention hashtag" rel="tag">#<span>TuesdayCodingTips</span></a> - Emplace into a vector of variants</p><p>`emplace_*` is an alternative to methods like `insert` or `push_*` in standard C++ containers. It allows you to construct the inserted object directly inside the container, passing all required parameters down to it, eliminating a needless copy/move along the way.</p><p>But what if you have a `std::vector&lt;std::variant&lt;Ts&gt;&gt;`? The interface is not flexible enough to control how to construct the variant object. There is however a hidden workaround.</p><p>Suppose your variant can be constructed from a cheap-to-construct type (trivial type, `std::monostate`, or similar). In that case, you can use that to initialize the new element and then invoke `std::variant::emplace` to override the element&#39;s memory with another type, presumably one that is expensive to copy or move.</p><p>Compiler Explorer link: <a href="https://godbolt.org/z/Yec1hvKzP" target="_blank" rel="nofollow noopener" translate="no"><span class="invisible">https://</span><span class="">godbolt.org/z/Yec1hvKzP</span><span class="invisible"></span></a></p><p><a href="https://techhub.social/tags/tips" class="mention hashtag" rel="tag">#<span>tips</span></a> <a href="https://techhub.social/tags/programming" class="mention hashtag" rel="tag">#<span>programming</span></a> <a href="https://techhub.social/tags/cpp" class="mention hashtag" rel="tag">#<span>cpp</span></a></p>
Jakub Neruda<p>Tip 86 of <a href="https://techhub.social/tags/TuesdayCodingTips" class="mention hashtag" rel="tag">#<span>TuesdayCodingTips</span></a> - Extendable logger pattern</p><p>I like my logs structured. If nothing else, I can log into a CSV file, load that CSV into Excel, turn on filters, and boom, I have a quite nice log analyzer tool. To get the maximum out of it, I need to be able to split my logs into as many columns as possible.</p><p>When creating a library where the user can provide their own logger implementation, you need to be very careful about the logger interface to minimize breaking changes in new library versions. Ideally, you want to be able to add new properties to the log without affecting existing logger implementations.</p><p>A typical interface method with one parameter per property won&#39;t do - every addition breaks the interface. What I like to do is wrap all loggable information in a struct and pass that to the logger interface instead. This allows me to add new properties with less fuss and evolve my libraries faster!</p><p><a href="https://techhub.social/tags/cleancode" class="mention hashtag" rel="tag">#<span>cleancode</span></a> <a href="https://techhub.social/tags/tips" class="mention hashtag" rel="tag">#<span>tips</span></a> <a href="https://techhub.social/tags/programming" class="mention hashtag" rel="tag">#<span>programming</span></a></p>
Jakub Neruda<p>Tip 85 of <a href="https://techhub.social/tags/TuesdayCodingTips" class="mention hashtag" rel="tag">#<span>TuesdayCodingTips</span></a> - Incomplete types and name demangling</p><p>While writing type-safe APIs, a &quot;tag&quot; type is often useful. It is nothing more than a forward declaration of a type that will never be fully defined, just for the sake of creating a template with a unique type.</p><p>Even without reflection, type-driven APIs can provide an opportunity to auto-generate (de)serialization code using typeid::name() utility. With two caveats:</p><p>You can&#39;t get the type info of an incomplete type<br />Unlike MSVC, both GCC and Clang will output mangled names<br />Luckily, both have a solution. While you can&#39;t get type info of an incomplete type, getting info of a pointer to an incomplete type is valid. You can trim the trailing star from the name. As for demangling, you can use the related ABI function (internally used by the c++filt tool).</p><p>Just remember to free your buffers, as said ABI function is written in a C-compatible way.</p><p><a href="https://techhub.social/tags/cpp" class="mention hashtag" rel="tag">#<span>cpp</span></a> <a href="https://techhub.social/tags/programming" class="mention hashtag" rel="tag">#<span>programming</span></a> <a href="https://techhub.social/tags/tips" class="mention hashtag" rel="tag">#<span>tips</span></a></p>
Jakub Neruda<p>Tip 84 of <a href="https://techhub.social/tags/TuesdayCodingTips" class="mention hashtag" rel="tag">#<span>TuesdayCodingTips</span></a> - C++ type trait for callable types</p><p>I recently asked myself: How do you make a reliable type trait for callable types so you can read their return values and call parameters?</p><p>The complexity lies in the fact that C++ has four distinct callable cases:<br />* A plain old function pointer<br />* std::function<br />* A class with an overloaded call operator<br />* A lambda</p><p>Some might argue that three of these are just iterations of the same concept, but lambdas have a compiler-specific/internal type, and custom classes usually don&#39;t contain all types for the call operator in their template (if they even have any).</p><p>The trick for defining the trait is thus in figuring out what the type of std::function would be should you construct it from your callable. Once you have that (through the use of declval and decltype), you can easily read the types from its template.</p><p><a href="https://techhub.social/tags/cpp" class="mention hashtag" rel="tag">#<span>cpp</span></a> <a href="https://techhub.social/tags/programming" class="mention hashtag" rel="tag">#<span>programming</span></a> <a href="https://techhub.social/tags/tips" class="mention hashtag" rel="tag">#<span>tips</span></a></p>
Jakub Neruda<p>Tip 83 of <a href="https://techhub.social/tags/TuesdayCodingTips" class="mention hashtag" rel="tag">#<span>TuesdayCodingTips</span></a> - Curious case of NLOHMANN_JSON_SERIALIZE_ENUM</p><p>I recently debugged a case where code generated by NLOHMANN_JSON_SERIALIZE_ENUM failed at invariant assertions deep in the nlohmann_json library. When examined under a debugger, the data looked in line with the invariants, but there were different from what I expected.</p><p>Upon expanding the macro, I found a static const array in the generated function body. This was suspicious as I knew the code is running multiple serializations in parallel and that static array was the only shared state between threads.</p><p>But the C++ standard clearly states such initialization is thread-safe! Except that our project had a particular MSVC flag set - /Zc:threadSafeInit- (which disables thread-safe initialization), for good reasons I can&#39;t talk about.</p><p>Takeaway - the problem isn&#39;t always in the code itself. Trust your gut feeling and dive into the compiler flags as well!</p><p><a href="https://techhub.social/tags/cpp" class="mention hashtag" rel="tag">#<span>cpp</span></a> <a href="https://techhub.social/tags/programming" class="mention hashtag" rel="tag">#<span>programming</span></a> <a href="https://techhub.social/tags/tips" class="mention hashtag" rel="tag">#<span>tips</span></a></p>
Jakub Neruda<p>Tip 82 of <a href="https://techhub.social/tags/TuesdayCodingTips" class="mention hashtag" rel="tag">#<span>TuesdayCodingTips</span></a> - C# exception filter</p><p>Ever found yourself in the situation where you have an exact same handling code for two (or more) selected exception types? C# has a nice way of deduplicating said code.</p><p>A `catch` block, processing a base exception type, can be optionally followed by a `when` clause, testing whether the exception in question is one of the derived types. A particular base exception can be used in multiple `catch` blocks, as long as at most one has no filter.</p><p>Furthermore, the filter expression is not restricted in any way, so you can interact with any object currently in the scope. If the filter expression throws, the exception is discarded, and the filter evaluates to false, moving to the next catch clause.</p><p><a href="https://techhub.social/tags/csharp" class="mention hashtag" rel="tag">#<span>csharp</span></a> <a href="https://techhub.social/tags/programming" class="mention hashtag" rel="tag">#<span>programming</span></a> <a href="https://techhub.social/tags/tips" class="mention hashtag" rel="tag">#<span>tips</span></a></p>
Jakub Neruda<p>Tip 81 of <a href="https://techhub.social/tags/TuesdayCodingTips" class="mention hashtag" rel="tag">#<span>TuesdayCodingTips</span></a> - Pattern matching a C++ enum</p><p>For a particular C++ application I wrote, I wanted to send an enum over a string-based protocol and pattern match on it on the receiving end to make sure that all possible values were handled.</p><p>You can&#39;t pattern match on enums in C++, but you can do it on a set of types. That begs the question - how, without reflection, (de)serialize a set of types? And how to minimize the amount of changes needed and check at compile-time whether you forgot anything?</p><p>Serialization is mostly easy, you can even use typeid if both communication ends are compiled with the same compiler. A C++20 concept (or static_assert) can make sure all required types are part of the supported set.</p><p>Deserialization requires a bunch of template trickery but can be implemented in such a way you don&#39;t ever need to touch it again.</p><p><a href="https://techhub.social/tags/cpp" class="mention hashtag" rel="tag">#<span>cpp</span></a> <a href="https://techhub.social/tags/programming" class="mention hashtag" rel="tag">#<span>programming</span></a> <a href="https://techhub.social/tags/tips" class="mention hashtag" rel="tag">#<span>tips</span></a></p>
Jakub Neruda<p>Tip 80 of <a href="https://techhub.social/tags/TuesdayCodingTips" class="mention hashtag" rel="tag">#<span>TuesdayCodingTips</span></a> - C# lambdas and mutability rules</p><p>Mutability rules in C# are funky. While trivial types are passed into functions by value, they are captured by reference for their usage in lambdas.</p><p>That can lead to stupid bugs - what if you want to spawn a bunch of threads and each thread has to index into some array with an index corresponding to the loop control variable? By the time that thread executes, the variable will be out of bounds.</p><p>If you need to do something similar, your best bet is to copy the loop control variable into some iteration-local variable and use it in the lambda instead.</p><p>Interestingly enough, while Python suffers from the same issue, it:</p><p>a) lets you explicitly copy the variable in the lambda definition<br />b) a `range` loop creates a distinct variable for each iteration, so it&#39;s not a problem anymore</p><p><a href="https://techhub.social/tags/csharp" class="mention hashtag" rel="tag">#<span>csharp</span></a> <a href="https://techhub.social/tags/programming" class="mention hashtag" rel="tag">#<span>programming</span></a> <a href="https://techhub.social/tags/tips" class="mention hashtag" rel="tag">#<span>tips</span></a></p>
Jakub Neruda<p>Tip 78 of <a href="https://techhub.social/tags/TuesdayCodingTips" class="mention hashtag" rel="tag">#<span>TuesdayCodingTips</span></a> - Many ways to read a file in C++</p><p>In C#, you can easily read a whole file using `File.ReadAllText` method. Obviously, things can&#39;t be as easy in C++.</p><p>You can choose the &#39;old way&#39; where you pre-allocate a buffer to which you can read the data. This process involves seeking the end of the file and figuring out how big it is. I dare you writing this version from memory first time without an error.</p><p>Then there is the &#39;iterator&#39; way. Just convert the stream object into an iterator and use that to initialize the string. Short, but not very memorable (not for me at least).</p><p>The easiest to remember (arguably) approach is to convert the file stream into a string stream and then easily extract the string contents from there.</p><p><a href="https://techhub.social/tags/cpp" class="mention hashtag" rel="tag">#<span>cpp</span></a> <a href="https://techhub.social/tags/programming" class="mention hashtag" rel="tag">#<span>programming</span></a> <a href="https://techhub.social/tags/tips" class="mention hashtag" rel="tag">#<span>tips</span></a></p>
Jakub Neruda<p>Tip 77 of <a href="https://techhub.social/tags/TuesdayCodingTips" class="mention hashtag" rel="tag">#<span>TuesdayCodingTips</span></a> - Multidimensional subscript operator</p><p>A quality-of-life C++23 feature has finally been integrated into Visual Studio (v17.12) after it was available in Clang (trunk) for quite some time. That feature is the support of the multidimensional subscript operator. In other words, you can overload `operator[]` to work with any number of arguments.</p><p>Furthermore, this feature goes hand in hand with std::mdspan - a non-owning multidimensional view over a 1D collection. This class is quite cool since storing 2D/3D tile data as a 1D array is a super-common micro-optimization technique in game dev.</p><p>Funnily enough, while Clang supported the operator long before MSVC, the trunk version as currently installed on Compiler Explorer doesn&#39;t have a support for std::mdspan.</p><p><a href="https://techhub.social/tags/cpp" class="mention hashtag" rel="tag">#<span>cpp</span></a> <a href="https://techhub.social/tags/programming" class="mention hashtag" rel="tag">#<span>programming</span></a> <a href="https://techhub.social/tags/tips" class="mention hashtag" rel="tag">#<span>tips</span></a></p>
Jakub Neruda<p>Tip 76 of <a href="https://techhub.social/tags/TuesdayCodingTips" class="mention hashtag" rel="tag">#<span>TuesdayCodingTips</span></a> - Type checking in Javascript?</p><p>When I want to prototype a simple GUI application, I often use plain old HTML + Javascript combo, because file protocol is cool, and setting up a node project is harder than I would like it to be.</p><p>But I would like to have some type checking in my Javascript code! Luckily, there is a cool feature in VS Code my friend @Ondřej Švanda told me about.</p><p>If you open your project as a workspace in code, you can then add a simple comment to the start of each Javascript file saying @ts-check. Then, if you write documentation for your functions, you can specify the types of parameters and return values.</p><p>And to top it all off, you can use a special @import comment to reference other files and bring them into scope, so the type-checking engine sees the whole picture. Even pressing F12 while a particular function is highlighted will navigate to that function&#39;s definition. Perfect!</p><p><a href="https://techhub.social/tags/javascript" class="mention hashtag" rel="tag">#<span>javascript</span></a> <a href="https://techhub.social/tags/tips" class="mention hashtag" rel="tag">#<span>tips</span></a> <a href="https://techhub.social/tags/programming" class="mention hashtag" rel="tag">#<span>programming</span></a></p>
Jakub Neruda<p>Tip 75 of <a href="https://techhub.social/tags/TuesdayCodingTips" class="mention hashtag" rel="tag">#<span>TuesdayCodingTips</span></a> - Installing links with NSIS through CMake</p><p>I recently debugged a case where program shortcuts created by NSIS launched the program from a bad working directory. And since the NSIS installer was created by CMake, it required some backward-bending to fix.</p><p>To customize the working directory for links created through CreateShortcut NSIS function, you have to call the SetOutDir function before calling CreateShortcut. To configure this through CMake, you have to set the NSIS commands manually through CPACK_NSIS_EXTRA_INSTALL_COMMANDS variable.</p><p>Since that quickly leads to some CMake symbol-escaping hell, I’d recommend using the trick from the code sample to achieve the desired outcome. The trick relies on the fact that lists of strings in CMake are just semicolon-delimited strings.</p><p><a href="https://techhub.social/tags/cmake" class="mention hashtag" rel="tag">#<span>cmake</span></a> <a href="https://techhub.social/tags/programming" class="mention hashtag" rel="tag">#<span>programming</span></a> <a href="https://techhub.social/tags/tips" class="mention hashtag" rel="tag">#<span>tips</span></a></p>
Jakub Neruda<p>Tip 74 of <a href="https://techhub.social/tags/TuesdayCodingTips" class="mention hashtag" rel="tag">#<span>TuesdayCodingTips</span></a> - Abstracting pipeline steps</p><p>While writing Azure DevOps pipelines, I often want to abstract a sequence of two or more actions into a function of sorts. For example, when publishing an artifact, I sometimes need to cherry-pick files from a given folder. This is normally done through the CopyFiles action, which cherry-picks the files into a staging folder that can be subsequently published.</p><p>To abstract this operation, create a template file that operates at the pipeline step level with a bunch of templated parameters.</p><p>You can call it at the level where you normally call tasks, just with the template keyword instead of a task. While running the pipeline, the template will be expanded into individual steps, so the pipeline logs will look like you didn&#39;t use any template at all.</p><p><a href="https://techhub.social/tags/devops" class="mention hashtag" rel="tag">#<span>devops</span></a> <a href="https://techhub.social/tags/programming" class="mention hashtag" rel="tag">#<span>programming</span></a> <a href="https://techhub.social/tags/tips" class="mention hashtag" rel="tag">#<span>tips</span></a></p>
Jakub Neruda<p>Tip 73 of <a href="https://techhub.social/tags/TuesdayCodingTips" class="mention hashtag" rel="tag">#<span>TuesdayCodingTips</span></a> - Resource Locator pattern</p><p>While catching up with Vittorio Romeo&#39;s blog, he mentioned a design pattern that immediately solved a problem I was dealing with in my C++ code.</p><p>Say you have a storage class for some resources, indexed by any sparse index (like strings). These resources are big so you wish to access them as (const) references. And let&#39;s assume you want to return expected/optional when the resource is not in the storage instead of throwing an exception.</p><p>That leads to a particularly ugly interface that you must use std::reference_wrapper in order to gain value semantics for references (required by expected/optional).</p><p>With the Resource Locator pattern, you split the interface into two methods. the first one is fallible and returns a unique resource access handle on success. The second is an infallible getter that consumes the handles. If the resources can&#39;t be deleted, this pattern can nicely streamline your code.</p><p><a href="https://techhub.social/tags/cpp" class="mention hashtag" rel="tag">#<span>cpp</span></a> <a href="https://techhub.social/tags/tips" class="mention hashtag" rel="tag">#<span>tips</span></a> <a href="https://techhub.social/tags/cleancode" class="mention hashtag" rel="tag">#<span>cleancode</span></a></p>
Jakub Neruda<p>Tip 72 of <a href="https://techhub.social/tags/TuesdayCodingTips" class="mention hashtag" rel="tag">#<span>TuesdayCodingTips</span></a> - Git worktrees</p><p>It is fairly common for me to have a bunch of open PRs at work. That requires me to have a bunch of clones of the same repo so I don&#39;t have to switch branches like crazy. But pulling changes on each clone gets tedious as the repo gets bigger.</p><p>That&#39;s where worktrees can come in handy. You clone the repository once and then through `git worktree add`, create a sort of shallow clone of that repo. They all share a single `.git` folder, so you&#39;re pulling the changes only once, but each worktree is a different branch, meaning a different version of the files. No two worktrees can be at the same branch simultaneously, which may be annoying at times.</p><p>Unfortunately, worktrees don&#39;t extend to submodules - those will each have their own `.git` folder.</p><p><a href="https://techhub.social/tags/git" class="mention hashtag" rel="tag">#<span>git</span></a> <a href="https://techhub.social/tags/tips" class="mention hashtag" rel="tag">#<span>tips</span></a></p>
Jakub Neruda<p>Tip 71 of <a href="https://techhub.social/tags/TuesdayCodingTips" class="mention hashtag" rel="tag">#<span>TuesdayCodingTips</span></a> - noexcept, vol 2</p><p>In tip 29, I shared how a noexcept keyword can be used in C++. Based on my recent experiences, I want to follow up with a few words of caution.</p><p>noexcept is more an API contract than an optimization. A transitive noexcept function still participates in stack unwinding. But because compilers can&#39;t diagnose misuse of the keyword, using it on transitive functions is a potential source of bugs.</p><p>I recently debugged a crash that appeared as an uncaught exception flying through a dynamic library interface. In reality, an exception escaped a noexcept boundary underneath and transformed into std::terminate. Very unpleasant to debug.</p><p>If you really want to use this annotation outside of leaf functions, either make sure to add static_assert for each function you&#39;re going to call, have a catch-all block, or make the noexcept conditional based on the signature of the called functions.</p><p><a href="https://techhub.social/tags/tips" class="mention hashtag" rel="tag">#<span>tips</span></a> <a href="https://techhub.social/tags/cpp" class="mention hashtag" rel="tag">#<span>cpp</span></a> <a href="https://techhub.social/tags/programming" class="mention hashtag" rel="tag">#<span>programming</span></a></p>
Jakub Neruda<p>Tip 70 of <a href="https://techhub.social/tags/TuesdayCodingTips" class="mention hashtag" rel="tag">#<span>TuesdayCodingTips</span></a> - JSON (de)serialization in C++</p><p>Many tips back, I wrote about JSON (de)serialization in C#. For C++, I bet every dev knows the brilliant nlohmann::json library at this point. But do you know how minimalistically can you use it?</p><p>The library allows for great deal of complexity and customization, but that is rarely desirable in a code that should be reserved for data models. Data structures that are subject of (de)serialization should be just stupid data objects with all members public and no associated logic.</p><p>nlohmann makes it very easy to work with such objects. The NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE macro just needs you to provide the name of the struct and all its members and it will generate necessary code for you. The downside is that you can&#39;t customize the serialized names of the attributes.</p><p>For enums, NLOHMANN_JSON_SERIALIZE_ENUM works very similarly, with the added bonus of being able to customize those JSON names.</p><p><a href="https://techhub.social/tags/cpp" class="mention hashtag" rel="tag">#<span>cpp</span></a> <a href="https://techhub.social/tags/programming" class="mention hashtag" rel="tag">#<span>programming</span></a> <a href="https://techhub.social/tags/tips" class="mention hashtag" rel="tag">#<span>tips</span></a></p>