They say learning by example is the best way to learn. I'd like to share one such example of API design process based on a recent discussion with one of my colleagues: https://medium.com/@nerudaj/a-case-study-on-api-design-f22a8665cf2d

If you follow my #TuesdayCodingTips, this showcases a practical application of many of them.

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

#CleanCode #SoftwareArchitecture #SoftwareEngineering

A case study on API design - Jakub Neruda - Medium

They say learning by example is the best way to learn. Today, I want to share one such example of API design process on a concrete piece of code. This innocent-looking function declaration was a part…

Medium

Tip 87 of #TuesdayCodingTips - Emplace into a vector of variants

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

But what if you have a `std::vector<std::variant<Ts>>`? The interface is not flexible enough to control how to construct the variant object. There is however a hidden workaround.

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's memory with another type, presumably one that is expensive to copy or move.

Compiler Explorer link: https://godbolt.org/z/Yec1hvKzP

#tips #programming #cpp

Tip 86 of #TuesdayCodingTips - Extendable logger pattern

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.

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.

A typical interface method with one parameter per property won'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!

#cleancode #tips #programming

Tip 85 of #TuesdayCodingTips - Incomplete types and name demangling

While writing type-safe APIs, a "tag" 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.

Even without reflection, type-driven APIs can provide an opportunity to auto-generate (de)serialization code using typeid::name() utility. With two caveats:

You can't get the type info of an incomplete type
Unlike MSVC, both GCC and Clang will output mangled names
Luckily, both have a solution. While you can'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).

Just remember to free your buffers, as said ABI function is written in a C-compatible way.

#cpp #programming #tips

Tip 84 of #TuesdayCodingTips - C++ type trait for callable types

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?

The complexity lies in the fact that C++ has four distinct callable cases:
* A plain old function pointer
* std::function
* A class with an overloaded call operator
* A lambda

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't contain all types for the call operator in their template (if they even have any).

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.

#cpp #programming #tips

Tip 83 of #TuesdayCodingTips - Curious case of NLOHMANN_JSON_SERIALIZE_ENUM

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.

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.

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't talk about.

Takeaway - the problem isn't always in the code itself. Trust your gut feeling and dive into the compiler flags as well!

#cpp #programming #tips

Tip 82 of #TuesdayCodingTips - C# exception filter

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.

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.

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.

#csharp #programming #tips

Tip 81 of #TuesdayCodingTips - Pattern matching a C++ enum

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.

You can'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?

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.

Deserialization requires a bunch of template trickery but can be implemented in such a way you don't ever need to touch it again.

#cpp #programming #tips

Tip 80 of #TuesdayCodingTips - C# lambdas and mutability rules

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.

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.

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.

Interestingly enough, while Python suffers from the same issue, it:

a) lets you explicitly copy the variable in the lambda definition
b) a `range` loop creates a distinct variable for each iteration, so it's not a problem anymore

#csharp #programming #tips

Tip 78 of #TuesdayCodingTips - Many ways to read a file in C++

In C#, you can easily read a whole file using `File.ReadAllText` method. Obviously, things can't be as easy in C++.

You can choose the 'old way' 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.

Then there is the 'iterator' 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).

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.

#cpp #programming #tips