* Source files should include headers in the following order:

  * If the file is C/CPP (foo.cpp), the corresponding header of this
    file (foo.h)
  * Standard C/C++ library headers. On Unix-like systems, OS API
    (POSIX) headers may also be in this group.
  * OS API headers.
  * Other third-party headers, including src/thirdparty.
  * Our headers from src/.

  Separate these #include groups by an empty line.

* When a CPP file implements a function from a C header, copy the
  signature verbatim to the CPP file. This includes the case when a C
  type from the signature is available in the std namespace, e.g.
  size_t (<stddef.h>) vs std::size_t (<cstddef>).

* In both C and C++ code, a pointer is not expected to be null (unless
  the opposite is explicitly stated in the documentation) in case it
  implies an arbitrary sized memory region (e.g. array, C string,
  void*, etc.) rather than a pointer to a single object. Other pointer
  types are handled as follows:

  * C: A function from a public C API should expect a pointer argument
    to be null. If null is an invalid value, the routine should either
    report an error or (if there's no way to do that) do nothing.

  * C++: In C++ code, raw pointers are only used to express optional
    references and thus always expected to be null. Exceptions to
    this rule should be revised to use references (or
    reference_wrapper), or at least be explicitly documented.

    In case of errors, a function returning a created object via a
    smart pointer should either throw an exception and never return
    null (this is the preferred way), or never throw and return null
    to signal an error.

* Don't use UTF-8 string literals (u8""). Since C++20, their type is
  changed from char to char8_t, breaking the backward compatibility.

  To insert UTF-8 bytes in a string, use 3 digit octal escape
  sequences. Don't use hex sequences, as they consume as much valid
  hex characters as possible. The only exception is when they are
  separated from the rest of the string by literal concatenation:

    "Copyright" "\xC2\xA9" "2018" (gives "Copyright ©2018")

  When used in translatable strings, such escape sequences cannot be
  assigned to macros for readability, since the xgettext tool does not
  understand macros. That is, the following will not work as expected:

    #define COPYRIGHT_SIGN "\302\251"
    ... = gettext("Copyright " COPYRIGHT_SIGN "2018");

  Instead, the escape sequence should be put inline (here we separate
  it from the rest of the string for better readability):

    ... = gettext("Copyright " "\302\251" "2018");

* In general, define arrays without an explicit size. When the number
  of elements in the array must match some constant, it should be
  enforced with a static_assert() following the array definition.

  The reason is that specifying an array size (both for plain and
  std::array) when using aggregate initialization is broken by design:
  it allows you to provide fewer values than specified by the array
  type, and compilers don't even warn about this. This is especially
  dangerous when the size comes from a constant that may one day
  become N times larger.

* Don't use locale-dependent routines.

  * Use functions from "dpso_utils/str.h" instead of <ctype.h>.

  * Use format() and toStr() from "dpso_utils/str.h" instead of
    printf() functions or anything that depends on them, like
    std::to_string().

    Even though std::to_string() overloads for integers are not locale
    dependent, we still forbid them to avoid the possibility of
    accidentally using overloads for floating-point types. We can
    switch back to std::to_string() once we have C++26: since C++26,
    all std::to_string() work as std::format("{}", v) and thus don't
    depend on the locale.

  * Use std::from_chars() for int-to-string conversion instead of C
    library functions like strto*() or anything that depends on them,
    like std::sto*() from <string>.

  * Use getErrnoMsg() from "dpso_utils/os.h" instead of strerror().

* Don't use C99/C++11 strftime() format specifiers in cross-platform
  code. They are not supported by old MSVCRT versions, and MinGW
  doesn't provide replacements as it does for pritnf() functions.

* Use underscores instead of hyphens in CMake target names.

* When specifying the language standard in CMake, use *_STANDARD and
  *_STANDARD_REQUIRED properties instead of target_compile_features().

  The reason is a bug in versions before 3.22: when the standard set
  by target_compile_features() is less than or equal to the compiler
  default, CMake ignores the disabled *_EXTENSIONS property and does
  not add the necessary flag to disable language extensions (e.g. an
  explicit -std=c++* instead of the implied -std=gnu++* in GCC).

* All global configuration macros and CMake options have DPSO_ prefix.
  A CMakeLists file should explicitly set the macro value in
  target_compile_definitions() rather than leaving it undefined; an
  undefined macro may be set to a default value in the source code,
  and that default may not match the CMake option.

* When creating translatable strings with plural forms, don't format
  the singular form especially, like:

    ngettext("One apple", "{count} apples", n)

  The English singular form always implies 1, so it's technically fine
  to use "One apple" instead of "{count} apple". However, some other
  languages use the singular form not only for 1, and such a special
  English case in the source string can trick people into doing an
  analogous translation implying a single item instead of using a
  generic form with a number.

  To avoid this issue, use the same form as for the plural, and handle
  the special case separately:

    if (n == 1)
      gettext("One apple")
    else
      ngettext("{count} apple", "{count} apples", n)
