ALib C++ Library
Library Version: 2412 R0
Documentation generated by doxygen
Loading...
Searching...
No Matches
ALib Programmer's Manual

Table of Contents

1. Introduction

Welcome the Programmer's Manual of the ALib C++ Class Library!

This manual explains the structure and concepts of the library and it's general use. The library is divided into "modules" which together comprise the actual library functionality. Please note, that for each of these modules a dedicated Programmer's Manual exists, which solely address a module's field of application.

The module's entities reside in inner namespaces of the libraries root namespace alib, which consequently is rather quite empty - apart from tons of type aliases. (The rationale for these aliases is explained here.) The majority of the remaining code in that namespace is related to organize the modules.

It becomes quite obvious that, before talking any further about this library, all about ALib Modules has to be explained.

2. ALib Modules

Being a "general purpose" C++ library, quite different areas of programming are addressed by ALib. This is why the library code and documentation is organized into different "modules".

Note
The term module relates to the planned future C++ language feature with the same name. As of today C++ modularization is not technically used with ALib, yet. Experimental support for implementing ALib Modules as C++ modules is planned for one of the next release, probably in 2025. It is expected that ALib Modules can be 100% translated into true C++ 20 modules.

The rationals for organizing the library in modules are:

  1. Design Principle of Orthogonality
    The modularization of the library is first of all a design principle, forcing library features to "fall into place" and make types and methods "orthogonal". (Orthogonal software can be considered as the opposite of "spaghetti code".)
  2. Comprehensible
    Users of the library are able to grasp the overall feature set much faster due to the modularization. As an example, the features of module ALib Expressions, is currently more than 25 bullet points. A user not interested in run-time expression evaluation, will not be bothered with that, because the module has an own namespace, separated reference documentation and an own Programmer's Manual.
  3. Well Defined Library Bootstrapping
    As explained in a later chapter, bootstrapping a software process written in C++ needs to obey to some certain rules which the programming language itself does not provide support for. ALib Modules tremendously helps organizing the library bootstrap process.
  4. Minimum Footprint
    Modules can be compiled and used separately from others, as long as there is no dependency between modules. When using CMake to build the library, all module-dependencies are automatically resolved: If a using application just needs a single module, that module is all that has to be specified with CMake. The provided scripts select the dependent modules and corresponding source files automatically.
  5. Build-Speed
    The ability to just partly compile (and use) ALib also increases build speed. Not only that of the library itself, but also that of the user code (due to the selective header inclusion).

2.1 Normal Modules vs. ALib Camps

ALib distinguishes between normal modules and so called ALib Camps. This term is explained in a later chapter. For now, it is sufficient to know that an ALib Camp is a more "heavy-weighted" entity, compared to a normal module.

2.2 Overview Of Modules

2.2.1 Non-Camp Modules

The following table lists all non-Camp modules in alphabetical order:

Name
(Programmer's Manual)
Namespace
(Reference Documentation)
Description
BitBuffer alib::bitbuffer Provides types to read and write data into a stream of bits in a platform-independent manner. Furthermore different compression algorithms are given.
Boxing alib::boxing Implements "auto-boxing" for C++, known from higher level programming languages. Transparently wraps C++ values of arbitrary type together with run-time type information into very lightweight objects. In other words, this library provides "type-safe void pointers which support virtual function calls", provides type-safe a variadic argument paradigm and much more.
Characters alib::characters Mitigates C++ character type, character pointer and character array hell.
Containers alib::containers ALib container types. Furthermore tools to enable the containers of the C++ Standard Library to use alib allocators are included.
Enums alib::enums Extensions for otherwise limited C++ enum types.
Monomem alib::monomem Monotonic allocation utilities to avoid heap-memory usage. Together with the concepts introduced with module ALib Containers, the amount of potential use cases for monotonic allocation are tremendously extended. Note that the cost of heap allocation is largely underestimated by programmers.
Singletons alib::singletons Implements the singleton paradigm that overcomes the Windows OS DLL boundaries (caused by Window DLL's local data segments), while under other operating systems chooses a faster, straight-forward implementation.
Strings alib::strings With a least intrusive design offers compatible, lightweight, secure, efficient, complete and convenient C++ string classes.
Threads alib::threads Low-level thread primitives.
ThreadModel alib::threadmodel High-level multithreading tools.
Time alib::time A few useful tools for otherwise limited C++ scoped and non-scoped enum types.

2.2.2 ALib Camps

The following table lists all "ALib Camps" in alphabetical order.

Name
(Programmer's Manual)
Namespace
(Reference Documentation)
Description
ALox alib::lox A very unique and powerful approach to implement a software's debug and release logging facilities.
BaseCamp alib::lang::basecamp Fundamental types related to the operating system, but also fundamental concepts for the C++ language, like the sophisticated standard ALib throwable type Exception, text formatting tools and other things that found no other home.
CLI alib::cli Implements parsing and partly the processing of command line arguments (CLI stands for "command line interface"). In case of pure CLI software, provides tools for formatted and localized help and result output.
Configuration alib::config Transparently encapsulates access to configuration or other input data coming from various sources, like command line parameters, environment variables, INI-files, 3rd-party configuration databases, etc. Support for persistent write-back of such data is given.
Expressions alib::expressions Expression parser, compiler and a virtual machine for evaluation of compiled expressions. Excels in respect to speed and flexibility and is very easy to adopt in own projects.
Files alib::files Directory and File scanner, optionally filtering with ALib Expressions.

2.3 Module Dependencies

2.3.1 Mandatory Dependencies

One feature of ALib is that active support for the use of just a subset of its modules is provided. Now, with the selection of a single module, all modules that the select one depends on, have to be added to that specific reduced library built. For example, module ALib Strings does not compile without the availability of module ALib Characters.

The following diagram shows the mandatory dependencies between the modules:

Note
  • The light blue modules show "normal" ALib Modules.
  • The dark blue modules show ALib Camps.
  • The two small rectangles on the left side of some of the module boxes indicate that a module has initialization and termination functions that needs to be invoked with bootstrapping a process, respectively terminating it. As it can be seen (and will be discussed later) this is true for all ALib Camps, but also for some of the normal ones.
The exact meaning of these differences will be discussed later in this manual.

2.3.2 Optional Dependencies

Besides the mandatory dependencies shown in the graph above, also optional relations between the modules exist. An optional relationship from module A to module B could be phrased as:
"Module A provides more/extended functionality in case that module B is included in the library built.".

These relationships are documented with each module's Programmer's Manual. Furthermore, within the reference documentation, you will find sentences like "This feature is available only if the module ALib XYZ is included in the ALib Distribution."

The following diagram shows the optional dependencies:

2.4 Impacts of Module Dependencies

If a user wishes to use and compile only a subset of ALib, the fact of having (selectable) modules that are connected through a set of dependencies, has to be taken into account in different ways.

To denote a full or reduced version of ALib, the term ALib Distribution is used.

2.4.1 Impact on Source Selection / Compilation

The selection of ALib library source code to compile (either to a dedicated library file or directly into a custom software unit) depends on the permutation of selected modules. Now, as the modules have dependencies, all sources of all dependent modules have to be (recursively) selected.

ALib comes with a set of CMake scripts that perform such selection. Even if CMake is not used for the daily build process, the scripts may be used once to select the source and header files needed to feed them into the build system in place.

All details about the build process are given in section 6. Building The Library.

2.4.2 Impact on Header File Inclusion and Code Selection

ALib header files are designed to be always includable, without the precondition to include other files before. For example, to work with type AString, it is enough to state

    #include "alib/strings/astring.hpp"

at the top of the compilation unit in question. Through the recursive header inclusion, the very first header that is processed is always:

    alib/alib.hpp

This header analyzes the given set of compiler symbols which define the modules included in a distribution. These symbols are named ALIB_MODULENAME and thus enumerate to:

Each symbol that is not given will be defined as either 0 or 1, by resolving all dependencies of those symbols given.

2.4.3 Impact on Bootstrapping

Selected modules have to be initialized in the right order. "Most independent" have to be initialized first, dependent modules afterwards. All information about bootstrapping the library is provided later in this manual, namely in chapter 4. Bootstrapping ALib.

2.4.4 Single Threaded Library Compilation

Excluding module ALib Threads from an ALib Distribution has the special effect of compiling ALib for single-threaded execution. Due to the fact that ALib uses class Lock and sibling types from this module for all its "mutex locks", leaving out this module removes just all concurrency protection from every remaining module!

The advantage of this approach lies of course in performance and code size of single-threaded compilations, because setting a mutex is a redundant action there. It is important to understand that module ALib Threads is mandatory if concurrent access to library functions is performed.

In debug-compilations of the library, if module ALib Threads is omitted, single-threaded use is therefore asserted: An ALib assertion is raised when multi-threading is detected.

To detect multi-threading in debug-code, the underlying threading library (e.g., "pthreads" on GNU/Linux) has to be provided with the build. For this reason, the default CMake-script, which is described in later sections, does not depend on the inclusion of module ALib Threads, but just always searches the target system's thread library. If found, symbol ALIB_EXT_LIB_THREADS_AVAILABLE is passed to the C++ compiler.

The way to prevent searching and adding a thread library is by setting special CMake cached variable ALIB_CMAKE_SKIP_THREAD_LIB_SEARCH to true.

In general it could be said:

Note
In case of doubt, it is recommended to include module ALib Threads in an ALib Distribution.

...unless a user knows exactly what she is doing!

2.4.5 Common library files

There are a few source files and corresponding types in ALib that are not associated with a module, but rather belong to the library in general. The source files are located in subfolder alib/lang - without its subfolders (!) - and the corresponding C++ types are collected in the library's root namespace alib::lang.

These files are not further organized and have to be included in the build process independent of the selected modules.

2.5 The Term "ALib Distribution"

Within this manual, the distinct manuals of ALib Modules and within the reference documentation, the term "ALib Distribution" is frequently used. The term addresses the permutation of modules that have been selected from the overall set of modules, at the time of compiling the library. It might be in a user's interest to reduce ALib to a subset of its functionality and this way speed up compilation and decrease library footprint.

The omission of single modules might have "side effects" on other modules in that respect, that while they are still compilable, certain features might be dropped. If this is the case, such dropping is always documented with the feature (function, type, method, member, etc.).

As a sample, ALib Expressions can be compiled with the absence of module ALib Configuration. But in this case functionality for loading and automatically compiling nested expression strings defined in INI-files, command-line arguments, etc. is dropped.

2.6 Special Treatment For Modules ALox And Threads

A special treatment is granted to module ALox, by having header file alib/alox.hpp always included in any ALib Distribution. For this reason, the file is placed outside of directory alib/alox, and instead right inside the source's root folder ALib sources.

This allows a user to place ALox log statements in a compilation unit without the need of testing preprocessor symbol ALIB_ALOX or the use of macros IF_ALIB_ALOX and IFNOT_ALIB_ALOX, in the case that her sources should be ready to compile likewise in an ALib Distribution that does not include ALox.

Similar to this, macros ALIB_LOCK and ALIB_LOCK_WITH remain available in the absence of module ALib Threads, as already discussed in previous section 2.4.4 Single Threaded Library Compilation.

3. ALib Camps and special Module BootCamp

After the previous chapter has given detailed information about how ALib is structured into modules and that an ALib Distribution can be reduced to a subset of these modules, it is time to talk about ALib Camps.

3.1 Terminology

In short, an ALib Camp is an ALib Module, which

  1. Follows a defined "bootstrap" and "shutdown" process, and
  2. Is enabled to manage externalized resources.

Both techniques are required by most softwares, but are not well supported by the C++ language. This unfortunately is likewise true for the C++ language feature called "C++ Modules", introduced with language version C++ 20.

Details for both features (bootstrapping and externalized resources) is provided in the next chapter. For now, the following facts and terminology is important to understand:

  1. Those ALib Modules which require sophisticated bootstrapping and externalized resources, are named ALib Camps.
  2. Both features become available by adding a singleton class to a module which derives from class Camp.
  3. The concept can be used by custom code. Once the concept is understood, creating a custom Camp can help structure the boot process and the resource management of an application tremendously. Again: unfortunately, there is a huge lack of support in the C++ language for both fields of concern and hence are often challenging and error-prone parts of software.

With this in mind, specific module ALib BaseCamp can be explained. This module resides in source folder alib/lang/basecamp and introduces class lang::Camp and all tool types needed for bootstrapping and resource management.

It would be as simple as this, if we did not add something more to it: We defined this module as a home for classes where we did not think it made much sense to introduce an extra module for. Or, in other words: where we thought: Whenever a user of the library decides to include an ALib Camp in the ALib Distribution, then certain functionality would usually be needed and added likewise.
The module organizes this functionality by introducing inner namespaces in alib::lang. Those are:

In addition, source files found in folder alib/lang/message add types like Exception and Report to namespace alib::lang.

This functionality needs bootstrapping and resources already, and this is why module ALib BaseCamp is an ALib Camp itself. And finally, because this is the lowest and most fundamental of all ALib Camps, its name was chosen to be "BaseCamp"!

Let's recap this chapter quickly:

  • Some of the ALib Modules are ALib Camps
  • ALib Camps provide externalized resources and bootstrap management
  • Custom software might use this concept and create custom camp modules.
  • The most fundamental ALib Camp is module ALib BaseCamp and provides all that is needed to implement further ALib Camps.
  • in addition, module ALib BaseCamp provides certain standard functionality that all "higher level-" ALib Camps need.

3.2 Class Camp

As just mentioned, module ALib BaseCamp introduces class Camp and every other "high level" module, aka ALib Camp, presents a singleton instance of a derived type. For example, the singleton type of camp ALib Expressions is class ExpressionsCamp and the one of camp ALib CLI is class CliCamp.

These singleton objects - including that of ALib BaseCamp itself - are collected in namespace alib with global instances and are named in capital letters:

This is the basic setup for solving the two challenges: Bootstrapping and Resource Management. Before this manual now dives into the details, one final prerequisite should be given: ALib Camps may (and usually do) share resources and configuration data. Therefore class Camp implements its data members ResourcePool and Configuration as pointers.

4. Bootstrapping And Shutting Down ALib

The term "bootstrapping" means the one-time initialization of the library that is to be performed in an early stage of a software process. The counterpart of bootstrapping is "shutting down" the library.

Standard bootstrapping and shutdown of ALib is performed by invoking functions alib::Bootstrap and alib::Shutdown. Such invocation usually is performed as one of the first and last actions in function main(). In source code, this simply looks like this:

#include "alib/alib.hpp"
int main( int argc, const char **argv )
{
// save cli arguments to global ALib variables
alib::ARG_C= argc;
alib::ARG_VN= argv;
// bootstrapping
...
...
...
// termination of ALib
return 0;
}
const char ** ARG_VN
Definition alib.cpp:72
int ARG_C
Definition alib.cpp:71
ALIB_WARNINGS_RESTORE void Bootstrap(int alibVersion, int alibRevision, TCompilationFlags compilationFlags)
Definition alib.cpp:84
void Shutdown()
Definition alib.cpp:122

The following chapters explain the details behind these invocations, provide recipes to customize certain aspects of the process, and explain how - optionally - the provided mechanics can be leveraged for the bootstrapping and shutdown of custom code entities likewise.

Attention
If you are new to ALib, you should skip the rest of this chapter completely and rather continue reading next manual chapter 6. Building The Library.
The reason for this is that the recipe above is good for the standard use cases of ALib and that the rest of this chapter becomes more and more complex and probably very irritating to a new user!
So please go ahead, nothing to see here! 😅

4.1 Weak Support For Bootstrapping In C++

The following circumstances increase the complexity of bootstrapping:

1. The C++ Language:

The C++ language specification misses sophisticated support for bootstrapping a software process. Basically, the only two things that are guaranteed by the language are:
  • Global and static data will be initialized.
  • After this is done, a method called main() is invoked.
This specification especially misses an order of the initialization of global or static data. Note that data initialization might execute arbitrary constructor code (before method main() is invoked!), hence also the order of such custom code execution is random.

2. Resources And Configuration Data:

Bootstrapping becomes even more complicated with the use of
  1. Resources (for example externally managed string resources) and
  2. Configuration data (e.g., configuration files, command line parameters, etc.).
With ALib, such custom data sources might furthermore be provided by plug-ins, which means the code entity that performs the provision of resources and configuration data needs to be initialized before the data is used.

4. Module Dependencies:

The dependencies between the modules have to be respected during initializations. As mentioned already, dependent modules have to be initialized first.

4. Multithreaded Access:

Multithreaded software has to take preparations to avoid undefined behavior due to thread-racing conditions while accessing "shared resources", which is often simply shared memory. Such preparation involves a performance penalty. In the case that all modifications of the memory that is to be protected can be aggregated to a one-time initialization action during bootstrap, such precaution could be omitted if bootstrapping guaranteed a period of single threaded access.

4.2 Bootstrapping Non-Camp Modules

An ALib Distribution might consist of only "non-Camp" modules. Currently those are: BitBuffer, Boxing, Characters, Containers, Enums, Monomem, Singletons, Strings, Threads and Time,

In this case that such a reduced ALib Distribution is wanted, the snippet of function main() shown above is all that is needed, because their bootstrapping is done by namespace functions alib::Bootstrap, respectively alib::Shutdown. These two functions are internally changed depending on the ALib Distribution and thus a user of the library does not need to care on the details.

              b

Nevertheless, the details are listed here. In fact, only five of the non-camp modules provide any bootstrapping functionality. Each module provides either namespace functions Bootstrap or Shutdown (or both) for that. Those are:

Module Bootstrap function Shutdown Function
ALib Boxing alib::boxing::Bootstrap() (no shutdown necessary/available)
ALib Enums alib::enums::Bootstrap() (no shutdown necessary/available)
ALib Singletons (no bootstrap necessary/available) alib::singletons::Shutdown()
ALib Time alib::time::Bootstrap() alib::time::Shutdown()
ALib Threads alib::threads::Bootstrap() alib::threads::Shutdown()
Note
For that reason, these five modules have the small rectangular markers in the dependency graphs

More complex boot strapping strategies that are shown in the following chapters, will always under the hood and automatically care for the non-Camp modules. Therefore, a reader might easily forget about them!

4.3 Bootstrapping ALib Camps

When ALib Camps are included in the ALib Distribution, then things can become a little more complex.

But first of all the good news: Even in the usual case that one or more ALib Camps are included in an ALib Distribution, bootstrapping and shutdown of the library is done exactly the same as shown above. In other words:

Attention
The following chapters about bootstrapping should be read only if you want to modify the bootstrap process for one of the following reasons:
  • A custom ALib Camp was built, which should be integrated in the bootstrap process
  • Customized configuration and/or resource-data setups or mechanics should be implemented
  • Other custom reasons, not foreseen by the author of this text.

If the above does not apply to your field of application, all you might need to know is that functions alib::Bootstrap and alib::Shutdown are internally largely extended in the presence of ALib Camps but, it is still the same simple call.

4.3.1 ALib Camps Bootstrap Interface

For bootstrapping and shutdown, types derived from class lang::Camp need to implement two protected, abstract methods, namely

Both methods are invoked more than once: Bootstrapping is done in three phases, defined by enumeration BootstrapPhases and shutdown is done in two phases, defined by enumeration ShutdownPhases. The implementations of the methods need to switch upon the given levels and perform different tasks.

Each phase will be finished for all camps, before a subsequent phase is initiated. The loop over the phases and its inner loop over the list of camps is implemented with namespace functions:

Note
These new overloaded versions are available only with inclusion of header alib/lang/basecamp/bootstrap.hpp, which in turn is only available with inclusion of the ALib BaseCamp in the ALib Distribution.

The parameterless versions we had seen before are declared in header file alib/alib.hpp, and thus "always" available.

4.3.2 Bootstrap Phases

With function Bootstrap, an outer loop over all phases is initiated. An inner loop iterates over all camps, in order of their "dependency level". For each combination of phase and camp, virtual method Camp::bootstrap is called. This ensures that for each phase, "low level camps" are initialized first and those of "higher level" are initialized next.

The three phases are defined as follows:

  1. BootstrapPhases::PrepareResources
    All initialization actions that exclusively involve static data, is deemed to be performed in this phase. For example, the registration of box-functions is to be done in this phase.

    The phase received its name from the fact that the ResourcePool is already available and a camp is supposed to feed its static default resource strings to the instance retrieved with Camp::GetResourcePool.

    More on this topic will be explained in a later chapter.

  2. BootstrapPhases::PrepareConfig
    In this phase, the configuration object is available and receivable with Camp::GetConfig. It may now be extended with plug-ins, but access to configuration variables is not allowed yet!
    For most camps there is not much to do in this phase.
    One important action usually placed here is to initialize static ALib Enum Records. If - as it is recommended - record definitions are given using externalized strings, then this is definitely the right place to invoke EnumRecords::Bootstrap for each enumeration in question.
    Note
    While the enumeration records are static data and could also be initialized in the first phase, after the definition of resources, placing it in this phase allows camps of higher dependency levels to modify (patch!) the resources of a lower level camp - before their use.

  3. BootstrapPhases::Final
    In this phase, final initialization steps may be performed.
    Only in this phase the start of threads is allowed (!), as any mandatory ALib initializations which are not protected against racing conditions are deemed to be performed in the previous phases.
    Furthermore, access to configuration variables is allowed in this phase.

4.3.3 Shutdown Phases

With function alib::Shutdown(ShutdownPhases targetPhase , an outer loop over the two shutdown phases is initiated. Again, an inner loop iterates over all camps, but this time in reverse order of their "dependency level". For each combination of phase and camp, virtual method Camp::shutdown is called. This ensures that for each phase, "high level camps" are shut down first and those of "lower level" are shutdown later.

The two phases are defined as follows:

  1. ShutdownPhases::Announce
    Within this phase, a camps can rely on the full stability of a software and all camps. Only actions might be taken that do not result in defects if features of a camp are still used. Typical samples of actions to perform with this phase are writing out configuration data or making an application's state persistent, to be able to restore it with a next run of a software.

  2. ShutdownPhases::Destruct
    This is the "true" shutdown phase. After this phase was invoked, a camp is obligated to have freed all its resources and is allowed to be dysfunctional afterwards.

4.4 List "Camps"

For default bootstrapping and shutdown, this is almost all we need to know. The remaining question is: Where is the list of ALib Camps, which is used for the inner loops of functions Bootstrap and Shutdown, defined?

Just like the overloaded, parameterized Bootstrap and Shutdown functions, the list becomes available with the inclusion of camp ALib BaseCamp and with including its header file alib/lang/basecamp/bootstrap.hpp. There, the declaration alib::CAMPS is given, along with function alib::BootstrapAddDefaultCamps, which fills list alib::CAMPS in the right order. The right order means: respecting the dependency hierarchy and permutation of camp basecamp::BaseCamp and those other camps, which might be included in ALib Distribution.

With a complete ALib Distribution, the list will be:

  1. alib::BASECAMP
  2. alib::CONFIG
  3. alib::ALOX
  4. alib::CLI
  5. alib::EXPRESSIONS
  6. alib::FILES

Again, the list is traversed from top to bottom with bootstrapping and in reverse order when the camps are shut down.

Now, if function alib::Bootstrap is invoked without explicitly filling the list beforehand, (right as it was shown in the introductory section of this chapter), then the empty list will be detected and function BootstrapAddDefaultCamps is automatically invoked.

In later sections of this manual, options for modifying these defaults will be demonstrated.

4.5 Standard Bootstrapping

With the knowledge taken from the previous sections, it is now easily understood what was said in the introductory chapter:

Standard bootstrapping of ALib is performed by invoking function Bootstrap

Consequently the simple version of bootstrapping was given like this:

    int main( int argc, const char **argv )
    {
        alib::Bootstrap();
        ...
        ...

This standard invocation of function Bootstrap not only fills the list alib::CAMPS, but also invokes overloaded function
alib::Bootstrap(BootstrapPhases targetPhase, lang::Camp* targetCamp, int,int,TCompilationFlags).
with parameters:

The loop iterating the phases, starts with the first phase that the camps have not been bootstrapped with, yet, and ends with the given targetPhase. With that, all phases are executed in above sample. And because the target camp is set to the last one in the list, the inner loop covers all camps.

What was not discussed, yet is when the instances of ResourcePool and config::Configuration are created and how the corresponding pointer members Camp::resourcePool and Camp::config are set.

This is how this is done:

  • An instance of class ResourcePool is created before executing the first phase PrepareResources and is distributed among the camps.
    The distribution is performed in reverse order, starting with the given targetCamp and from there to the lower level camps.
  • The same is done with a new instance of class Configuration, but this is done only before executing second phase PrepareConfig.

The following schematic summarizes this:

  • Outer loop: Phase PrepareResources
    • Creation of a resource pool.
    • 1st inner loop: Distribution to lower level camps in reverse (descending) order.
    • 2nd inner loop: Invocation of bootstrap( PrepareResources ) on all dependent camps, in ascending order.
  • Outer loop: Phase PrepareConfig
    • Creation of configuration instance.
    • 1st inner loop: Distribution to lower level camps in reverse (descending) order.
    • 2nd inner loop: Invocation of bootstrap( PrepareConfig ) on all dependent camps, in ascending order.
  • Outer loop: Phase Final
    • Invocation of bootstrap( Final ) on all camps, in ascending order.

With this information the standard process of bootstrapping is well defined. The following chapters introduce different ways to customize bootstrapping.

4.6 Customizing The Bootstrap Process

4.6.1 Bootstrapping Custom Camps

As explained in previous chapter 4.1 Weak Support For Bootstrapping In C++, the reason why bootstrapping ALib is a non-trivial task, does not lie in specifics of this library, but in a general lack of support for bootstrapping in the C++ language. Any more complex software needs to solve this task.

Consequently, it might be a reasonable decision, to adopt what this library offers and use this concept and the mechanics to bootstrap custom code units likewise.

Doing this is rather simple. The steps are:

  1. Create a custom type derived from class Camp along with a global singleton instance of this type.
  2. In the main() function, invoke BootstrapAddDefaultCamps.
  3. Next, use method List::PushBack to add the static instance of the custom camp type(s) to variable CAMPS.
  4. Perform bootstrapping by invoking function alib::Bootstrap.
Note
A complete source code sample is given with the tutorial of ALib Camp CLI. Here is the corresponding excerpt from the its main()-function:
int main( int argc, const char **argv )
{
alib::ARG_C = argc;
alib::ARG_VN = argv;
// 1. Add our custom camp to the list of camps
alib::CAMPS.PushBack( &SAMPLE_CAMP );
// 2. Initialize all modules
...
...
ALIB_API List< MonoAllocator, lang::Camp * > CAMPS
void BootstrapAddDefaultCamps()
Definition bootstrap.cpp:59

In more complex scenarios, a software can also create more than one module. Each camp may be appended to list alib::CAMPS (see step 3). If these camps are following a dependency hierarchie, then the lowest (most independent) camps should be attached first. The target camp of the bootstrapping (step 4) is the latest camp added.

This recipe addresses the simple cases. When a software chooses to hook itself into the bootstrap mechanism of ALib as shown above, the resources and configuration data (including for example INI-files) are shared between all modules and thus contain both, data of ALib and that of custom camps.

Often, this is not a desired scenario, because:

  • A software does not want to bother an end-user with the rather technical resources and configuration variables provided by ALib. Cluttering custom data with ALib data should be avoided
  • A software might want to disallow an end-user to modify configuration data and/or resources of ALib.
  • etc.

How these goals can be reached is explained in the next section.

4.6.2 Using Custom Resources and/or Configuration Plug-ins

The default implementation of class ResourcePool used with non-customized bootstrapping is of type LocalResourcePool and this does not externalize the resource strings. To use either alternative, built-in type ConfigResourcePool or a custom implementation of the interface that attaches to a 3rd-party "resource pool backend", all that needs to be done is to create such instance and pass it to method Camp::BootstrapSetResourcePool of the last (!) camp in alib::CAMPS.
This has to be done before the first invocation of function Bootstrap.

As documented with class Configuration, the type allows being equipped with custom mechanics for loading external data. To do so, the bootstrapping process has to be interrupted after the configuration is internally created. This is done by explicitly specifying BootstrapPhases::PrepareConfig with function Bootstrap.

Now, the configuration can be accessed with Camp::GetConfig and is manipulated as wanted. When done, bootstrapping is finalized by invoking Bootstrap a second time with argument BootstrapPhases::Final.

The schematic that shows both approaches at once, then looks as follows:

  • Create instance myResources on the last camp in alib::CAMPS.
  • Invoke BootstrapSetResourcePool(myResources).
  • Invoke Bootstrap(PrepareConfig).
  • Modify configuration object received with GetConfig (on any of the ALib Camps, because they still all share the same instance).
  • Invoke Bootstrap(Final).

4.6.3 Using Multiple Resources and/or Configuration Instances

With the bootstrap process described so far,

  • different instances of a resource pool, and
  • one instance of class Configuration, but customized, e.g., with custom ConfigurationPlugin

To also add a different configuration instances, method BootstrapSetConfig is given, just like the already introduced method Camp::BootstrapSetResourcePool.

Both methods may be called on arbitrary camps, before starting bootstrapping the corresponding phase.

An object set this way will not be overwritten when the corresponding bootstrap phase distributes instances from higher- to lower-level camps. Moreover, if the algorithm finds an object already set, then the distribution loop continues with this object!

This allows a rather natural way to separate all camps into logical sets that share one resource pool and/or configuration.

Nevertheless, to also distribute different configuration objects, the bootstrap phases have to be separated, as shown in the next section.

4.6.4 Separating Bootstrap Phases

The fact that method alib::Bootstrap(BootstrapPhases targetPhase.

is allowed to be called with setting parameter targetCamp to a "lower level" camp (one that is not the last of list alib::CAMPS), allows bootstrapping lower-level camps separately from bootstrapping higher-level ones.

As discussed in previous section, such approach might be needed in case that custom camps are integrated into the ALib bootstrap process.

Let's consider the following sample:

  1. Custom camp MyApp is derived from class lang::Camp
  2. In the main() function, BootstrapAddDefaultCamps is invoked.
  3. The singleton of MyApp is added to the end of list CAMPS (hence on the last ALib camp)
  4. Bootstrapping is fully performed on the second last singleton in list CAMPS.
  5. Bootstrapping is afterwards fully performed on singleton MyApp.

The consequences from this approach are:

  • The set of ALib Camps share a different resource pool and configuration object than MyApp.
  • During the bootstrapping of MyApp, built-in ALib Camps are already fully bootstrapped and functional.
Note
If you want to test that you have understood the mechanics of bootstrapping ALib, you might answer the following question for yourself: Why can step 5 above be done by simply invoking parameterless funcition alib::Bootstrap()?

Alternatively the same can be reached with the following recipe:

  1. - 3. same as above
  2. Create instance alib_Resources and set it on the last ALib Camp in alib::CAMPS.
  3. Invoke Bootstrap(PrepareConfig).
  4. Create an instance alib_Configuration and set it also on the last ALib Camps in alib::CAMPS.
  5. Invoke Bootstrap(Final).

4.6.5 Conclusion

We admit, that what was said above is a little complex. But once you understand why both recipes given above lead to the same very same result, you can tell that you understood ALib Bootstrapping!
With this toolset and the knowledge that:

  • Both recipes can be mixed,
  • Custom resource pool instances might load resources externally, instead of compiling them into an executable's data section,
  • Different camps may use different resource pools
  • The same is true for configuration data
  • Completely other custom operations can be performed in the different bootstrap phases defined with ALib Camps,
  • etc.

... bootstrapping a software becomes a very well defined and organized undertaking!

4.6.5 Customization Of Shutdown

Customization of shutting down ALib should be a seldom need and works the same in principle. Everything is done in reverse order. Function alib::Shutdown(ShutdownPhases targetPhase

has defaulted parameters targetPhase and targetCamp and if not specified alle camps in list alib::CAMPS are shutdown over both phases. For this, parameter targetCamp, this time defaults to the first camp in the list.

if targetPhase is specified as Announce then only the first phase is performed and custom actions might be taken before invoking the second phase ShutdownPhases::Destruct.

For simplicity, in contrast to bootstrapping the resource and configuration objects are destroyed right in the reverse loop of phase Destruct, in contrast to adding a post-process second loop and done with bootstrapping. Thus, a "lower" camp's resources and config objects might be already destroyed, at the moment the camp itself is destructed. Hence, phase Announce is the true last point of action in respect to an involvement of these objects.

4.7 Assuring Compilation Compatibility

Due to the facts that

  • ALib allows reducing to a subset of its functionality by changing the ALib Distribution, and
  • ALib provides a set of compilation flags, which for example switches default character sizes, or turns other features on or off, and also because
  • ALib follows the policy of having the freedom to quite radically changing the internal structure and types with any new version, in other words is never "downwards compatible",

it is important that a compiled ALib library must be incompatible to compilation units that actually use the library. For example, the footprint (size) of library types might be different.

With C++, if incompatible compilation units are mixed, often no warning is issued (neither by the compiler nor the linker) and the execution of a process might lead to very strange behavior which is irritating and hardly to debug.

To detect incompatible compilation settings and library versions, three preprocessor symbols have been defined. Each symbol has a corresponding namespace variable compiled into the library. The symbols and variables are:

Preprocessor Symbol Namespace Variable
ALIB_VERSION alib::VERSION
ALIB_REVISION alib::REVISION
ALIB_COMPILATION_FLAGS alib::COMPILATION_FLAGS

Along with that, namespace function alib::AssertALibVersionAndFlags is given, which expects comparison values for each of the namespace variables. The little trick here is, that the parameters are defaulted with the macros. Consequently, the parameters must not be given when the function is invoked.

Furthermore, the parameters are also silently (aka defaulted) added to overloaded functions alib::Bootstrap, which internally invokes AssertALibVersionAndFlags().

For this reason, nothing specific has to be done by a user of the library, as the checks are already performed with bootstrapping!

Note
With CMake builds, if CMake function ALibSetCompilerAndLinker is used with a custom target, all settings are guaranteed to be compatible to a library that is built within the same CMake process.

5. Multithreading Support

With ALib, switching support for multithreading on and off is not a preprocessor symbol. Instead, the support is dependent on selecting module ALib Threads into the ALib Distribution or not.

If it is not selected, certain macros and function remain to exists: They are just emptied and with that pruned by the preprocessor or optimized out bei the compiler.

This slight breaking of the rule - namely to have entities belonging to a certain module still existing when this module is not available - brings a huge benefit: All other modules and likewise the custom code library users, can be implemented without much code cluttering.

The gaol here is: Protection mechanisms against thread racing conditions should generally be included in any user's project, they are just pruned in case that code used with an ALib Distribution that misses module ALib Threads.

All further information is provided with the dedicated Programmer's Manual of module ALib Threads.

6. Building The Library

6.1 Overview

6.1.1 Platforms and Toolchains

As of today, ALib for C++ is compiled and tested under the following platforms and toolchain combinations:

  • GNU/Linux Arch 6.12.1, GNU C++ 14.2.1 / Clang++ 18.1.8, C++ 17/20/23, 32-Bit / 64-Bit
    (This is the main development platform.)
  • WindowsOS 10/11, MSC 19.42 (Visual Studio 2022), C++ 17/20, 32-Bit / 64-Bit
  • WindowsOS 10/11, MinGW, GCC 13.47 C++ 17/20, 64-Bit
  • macOS Sequoia 15.2, Apple M2 / ARM64, Apple Clang Version 16.0.0, C++ 17/20/23, 64-Bit
  • Raspberry 3, ARM, 64-bit OS, GNU C++ 12.2.0, C++ 17/20/23
  • Raspberry 4, ARM, 64-bit OS, GNU C++ 12.2.0, C++ 17/20/23

All development was performed in a) CLion/CMake (all platforms) and b) Visual Studio Community Edition 2022. All necessary IDE files are included in the repository.

Adoptions to other platforms, toolchains, and IDEs should be implementable with limited efforts. All relevant code which selects platform/toolchain specific libraries, intrinsics, etc., will expose a preprocessor error if a section fails due to an unknown environment. This allows quickly inserting the right platform/toolchain specific code at these places.

Note
We would be very happy to receive your feedback/input on necessary changes for other platforms!

Especially errors occurring in header alib/lang/integers.hpp might be quite likely for unknown platform / toolchain combinations. Here, a set of five compiler symbols might be passed using the build system (e.g., CMake), which are documented with symbol ALIB_SIZEOF_INTEGER.

The C++ compiler warning level is defaulted to the bearable maximum. This means, that the inclusion of ALib headers into a custom project's compilation process should never lead to compilation warnings or errors when similar high custom warning levels (of the including project) are used.

6.1.2 Reference Toolchain CMake

While project files for different IDEs might be provided with the codebase, the main development of ALib is performed using CMake scripts. Also, the relationship of source file and ALib Module selection is "officially defined" by CMake scripts.

The CMake scripts included in the ALib distributions are to be seen as the reference guideline to building ALib. The scripts may even be more precise (and up-to-date!) than the documentation you currently read itself.

Even for non-experienced users (in respect to CMake), the syntax of the file should be easily understood. So, do not hesitate to open and read the CMake files for your project setup reference.

A Microsoft Visual Studio solution and according project files which build an ALib DLL and the unit tests, are included in the ALib distribution. These may be used to compile an ALib library that includes all of ALib, which can be used for own projects.
Limited library projects that include only a selection of modules of ALib are not provided and thus have to be created if they are desired.

6.1.3 Library Installation

As of today, no installation process of a shared library version of ALib is available with the build process defined. Not installing a library has of course the disadvantage, that every software project needs to compile its own version of it, and the library is by default not shared between applications, even if compiled as a "shared library", respectively DLL.

While this may change in the future, the advantage of this approach is that an application has a lot flexibility in respect to compiling ALib using the exact set of features it desires.

Therefore, to enable a software to use ALib, the sources of the library have to become a part of the build process in any form. As usual, there are three possible basic options:

  1. Compiling ALib to a static library,
  2. compiling ALib to a shared library or
  3. compiling ALib sources directly into another software entity.

When this manual section talks about "building the ALib library", one of the three options is meant.

Note
Of course, a custom installation process can be established to place library binary and header files in corresponding system folders. It is just not provided today.

6.1.4 Unit Tests

An extensive set of unit tests is included in the distribution.

6.2 Performing A Test Compilation

Clone the ALib repository from ALib at GitHub to a place where you commonly store 3rd party libraries that your projects are using. Alternatively, download and unpack the ZIP file to that same place.

In this documentation, we will refer to this library location as the ALIB_BASE_DIR. After a fresh installation, it should contain at least the following subfolders:

    ALIB_BASE_DIR/build
    ALIB_BASE_DIR/docs
    ALIB_BASE_DIR/html
    ALIB_BASE_DIR/src
    ALIB_BASE_DIR/tools

To build the unit tests, perform the following steps:

  • open a console window and cd into directory:
      ALIB_BASE_DIR/build/cmake/unittests
    
  • create two subfolders from here, one named debug and the other release
  • cd into directory debug and type
      cmake ..
    
  • once CMake has run, type
      make -j
    

The compiled sample executable should have been created and you can start it with

./ALib_UT

Within a few seconds all unit tests should have been performed.

For a release build, the steps are similar. After you cd into the 'release' folder, the cmake command is:

cmake -DCMAKE_BUILD_TYPE=Release ..
Note
For the unit tests, Google gtest gets downloaded (once) and incorporated as a source project within the binary folder. This means:
  • This library does not need to be installed anywhere else on the system
  • This library gets deleted when deleting the CMake build folder (in the sample above named "debug" respectively "release").

6.3 A Step-By-Step CMake Sample

With using CMake, compiling and using ALib is very straight forward. This is because a set of easy to use CMake scripts is provided, of which one is to be included into the custom CMake script.

The following demonstrates this step-by-step along the sample project found in folder

    ALIB_BASE_DIR/src/samples/alox


Step 1: Creating the CMake file
A custom CMake file has to be created. To start with, the top of the file might look like this:

cmake_minimum_required( VERSION 3.18.0 )
project("ALox.Samples")


Step 2: Choose ALib Modules
The list of ALib modules to be included in the built is defined with CMake list variable ALIB_DISTRIBUTION which has to be set before invoking the ALib CMake script. If the list is left empty, it will be defaulted to "ALL", which chooses all ALib modules available.
In our sample, we add "ALOX", which chooses module ALox and all dependent modules.

list( APPEND ALIB_DISTRIBUTION "ALOX" )


Step 3: Set Other Feature Variables
Our project should be compiled using C++ 17. This is set with:

list( APPEND ALIB_COMPILER_FEATURES "cxx_std_17" )


Step 4: Include "ALib.cmake"
Now we are ready to include the main ALib CMake script:

include( ${CMAKE_CURRENT_LIST_DIR}/../../ALib.cmake )

Note that this sample is using a relative path. In a real-world sample, the path might as well be an absolute one. After the script is run, some global CMake variables and functions are defined. All of those are documented in the next chapter. Let us first continue the sample:


Step 5: Define A Library Project
We invoke CMake function ALibAddSharedLibrary, which creates a CMake target called "ALib_SharedLib" having all necessary settings:

ALibAddSharedLibrary()


Step 6: Define The Custom Project
Now we are good to define our custom project in a usual way:

add_executable ( ALoxSamples ${ALIB_BASE_DIR}/src/samples/ALox/sample.cpp )


Step 7: Add Compiler And Linker Settings
Our main project needs to share some ALib compiler and linker settings with the ALib library project. To achieve this we invoke CMake function ALibSetCompilerAndLinker:

ALibSetCompilerAndLinker ( ALoxSamples )


Step 8: Add Library Project To Custom Project
The final step is to add the library project to the custom sample project, which is again a standard CMake task:

target_link_libraries ( ALoxSamples PRIVATE ALib_SharedLib )

That's it. With this simple CMake file we have created a tailored ALib Distribution library and have linked it to our sample project, which is now ready to be built!

6.4 The Build Process In Detail

The previous chapter demonstrated the use of the CMake script ALib.cmake provided with ALib. In the following sections a reference documentation on all aspects of the script is given.

Along these lines, the build requirements of ALib is explained - also for users of any other build system. independent of the build-system used, the following information has to be collected and accordingly set:

  • The set of ALib source files that comprise the desired set of ALib Modules.
  • C++ Version 17 or higher (to be set with the compiler).
  • compiler symbols that select the ALib Modules have to be passed to the compiler.
  • compiler symbols that choose ALib features have to be passed to the compiler.
  • External library dependencies have to be determined.
  • Linker flags have to be defined.
Note
The complete set of symbols that the ALib library accepts from the compiler (usually -D option) are documented here.

6.4.1 Selecting ALib Modules

CMake list variable ALIB_DISTRIBUTION is an input/output list which defines the particular ALib Modules that should be included in the built. The script will process the values given and will extend the list to include all necessary modules that the given selection depends on.

The values correspond to the module names in upper case letters, hence ALOX, BOXING, CHARACTERS, etc...

If the variable is not set or contains special name "ALL", all modules are chosen to be built and included.

6.4.2 Variables For Directory and Source Code Definitions

The following CMake variables are available after the invocation of the script:

  • ALIB_BASE_DIR
    Defines the directory where the ALib project is stored.
    This variable may also be specified before entering the script to specify a different directory than the default, which is inferred from the full path name of the script invoked.
  • ALIB_SOURCE_DIR
    Defines the directory where the ALib source files are stored. The header files are located in the same directories.
    This variable may also be specified before entering the script to specify a different directory than the default, which is
          ${ALIB_BASE_DIR}/src
    
  • ALIB_SOURCE_FILES
    The list of source files (compilation units) needed to compile ALib.
  • ALIB_INCLUDE_FILES
    The list of header files needed to compile ALib.

6.4.3 Cached CMake Variables

The script will create a set of cached boolean CMake variables (which are variables that can be edited with CMake GUI tools and various C++ IDEs).

The following variables correspond directly to preprocessor symbols used for code selection and thus this list links to their corresponding documentation:

In addition, the following further cached variables are set:

  • ALIB_VERSION
    Defines the ALib library version. This variable cannot be changed, respectively will be overwritten on CMake generation. It is specified as a cached CMake variable just for the reason of presenting the ALib version to tools that allow to modify CMake settings.

    Furthermore, non-cached version variables ALIB_VERSION_NO and ALIB_VERSION_REV are set which hold the version number and revision number as separate integral values.

  • ALIB_CMAKE_VERBOSE
    If set to true, extended information will be printed with running the CMake script. See 6.5.1 Other Build Systems for details.
  • ALIB_CMAKE_SKIP_THREAD_LIB_SEARCH
    If set to true, the provided script will not search for a thread library on the target platform. For more information see chapter 2.4.4 Single Threaded Library Compilation.
  • ALIB_DEBUG_GLIB
    If true, symbols _GLIBCXX_DEBUG, _GLIBCXX_DEBUG_PEDANTIC and _GLIBCPP_CONCEPT_CHECKS are passed to the compiler.
  • ALIB_COVERAGE_COMPILE
    If true, option –coverage is added to CMake variables ALIB_COMPILER_OPTIONS and ALIB_LINKER_OPTIONS

6.4.4 CMake Build-Setting Variables

The script will create the following non-cached CMake variables, which can be used to define build-settings of custom projects:

  • ALIB_SYMBOLS
    Will contain the ALib preprocessor symbols, as defined by the cached variables. This variable can for example be used as a parameter to CMake function target_compile_definitions().
  • ALIB_COMPILER_WARNINGS
    Will contain compiler parameters to set the (high!) warning level used with compiling ALib. This variable can for example be used as a parameter to CMake function target_compile_options().
    Custom entries may be added to the list before invoking ALib.cmake. If entry "ALIB_SUPPRESS_COMPILER_WARNINGS" is found, that entry is removed and no compiler-specific warning settings are added to this symbol.
  • ALIB_COMPILER_OPTIONS
    Will contain parameters to be passed to the compiler when linking ALib. This variable can for example be used as a parameter to CMake function target_compile_options().
  • ALIB_LINKER_OPTIONS
    Will contain parameters to be passed to the linker when linking ALib. This variable can for example be used as a parameter to CMake function set_target_properties().
  • ALIB_EXTERNAL_LIBS
    Will contain a list of external libraries needed to build ALib. This variable can for example be used as a parameter to CMake function target_link_libraries().

In addition, the following non-cached variable is an input variable which may be set before invoking the script:

  • ALIB_COMPILER_FEATURES
    May contain CMake feature parameters to be passed to the compiler. This variable can for example be used as a parameter to CMake function target_compile_features().

6.4.5 CMake Functions Defined By The Script

The script will define the following CMake functions:

  • ALibAddStaticLibrary
    This parameterless function creates a static library target called ALib_StaticLib.
  • ALibAddSharedLibrary
    This parameterless function creates a shared library (under Windows OS a DLL) target called ALib_SharedLib.
  • ALibSetCompilerAndLinker( target )
    This function applies the settings of all variables listed in the previous section to the given target. In addition, the target's CMake property POSITION_INDEPENDENT_CODE is switched on.

6.4.6 Library Filename

The library's filename is determined by (non-cached) CMake variable ALIB_LIBRARY_FILENAME. If this variable is not set before invoking script ALib.cmake, then the name is determined automatically from the selected modules.

The name is used with functions ALibAddStaticLibrary and ALibAddSharedLibrary, introduced in the previous section.

If not provided, the name will be assembled according to the following set of rules:

  • The name starts with "alib_".
  • The library version is appended, for example "2412R0".
  • In the case of debug-builds, term "_DBG" is appended.
  • In the case that all modules are selected in the ALib Distribution, no further rule is applied.
  • Otherwise, each selected module's name is added in capital letters, separated by an underscore character ('_') unless a module is not "superseded" by another module that has a mandatory dependency to it.
  • As an exception, in the case that module ALib Strings is included (and no other module supersedes it) and furthermore, in the case that ALIB_DEBUG_STRINGS is set, suffix "_DBGSTRINGS" is appended instead of "_STRINGS" .
  • In the case that ALox is included, the suffixes _ALOX is extended by suffix NRL if release logging is switched off. In addition, suffix NDL is added with debug-builds that do not include debug logging. Note that both exception cover the non-default, unusual compilation cases in respect to the availability of debug- and release logging.

6.5 More Information

6.5.1 Other Build Systems

As already explained, the CMake build process is viewed to be the reference process for ALib compilation.

For noneCMake users, the selection of the exact minimum set of source and header files, is probably the most difficult (and annoying) task. In CMake we have separated the source selection into script:

        ALIB_BASE_DIR/build/cmake/ALibSources.cmake

which is invoked by the main script.

This script might be analysed to identify the source and header file dependencies of the different ALib Modules.

Likewise, script

        ALIB_BASE_DIR/build/cmake/ALibModules.cmake

might be analysed to get actual (and correct) information about module dependencies.

Furthermore, by setting CMake cache variable ALIB_CMAKE_VERBOSE to true, running CMake will write extended information that might be used and copied into the configuration files of other build systems or into IDE project files.

Among the data displayed is:

  • List of modules included in the distribution. (Modules that the user explicitly selected plus recursive dependencies)
  • List of source and header files that are needed for the build.
  • Resulting library filename.
  • Base folder of source files.
  • The compiler symbols (definitions) passed.
  • The compiler warning flags.
  • The compiler features and other flags.
  • The linker flags
  • External libraries used.

6.5.2 Choosing C++ Language Version

As demonstrated in the chapter A Step-By-Step CMake Sample (Step 3), CMake variable ALIB_COMPILER_FEATURES may be used to determine the C++ language standard for ALib targets and optionally for custom targets (i.e. if function ALibSetCompilerAndLinker is invoked for a custom target).

ALib requires C++ language level 17 as a minimum and is compatible with levels 20 and 23. ALib might perform better with higher language levels.

CMake provides other (maybe even more preferable) mechanics to determine/set the C++ language level, which of course may be used alternatively.

6.5.3 Copying The Sources

If CMake variable ALIB_SOURCE_COPY_TARGET_DIR is set before invoking CMake script ALib.cmake, then the source code (compilation units and header files) are copied to the directory specified in the variable. If the variable does not point to a valid directory, an error is raised and the CMake script is stopped.

The files copied represent exactly the set of files which are needed for compiling the combination of ALib Modules, which are optionally specified with list variable ALIB_DISTRIBUTION (see 6.4.1 Selecting ALib Modules).

This feature therefore can be used to create a fresh, filtered copy of the ALib source tree tailored to an application. Nevertheless, it is usually not recommended to do so, because source files that are not used by a combination of modules are neither included as header files, nor compiled. The feature is rather used by the ALib developers to verify module and source code dependencies.

6.5.4 External Library Dependencies

ALib has no mandatory dependencies to external (3rd-party) libraries. The following optional dependencies exist:

  1. Boost RegEx
    If enabled by compiler symbol ALIB_FEAT_BOOST_REGEX, a dependency to boost library component regex is made. As a result, the following ALib features become available:
  2. Standard C++ library
    Some support to make types of the C++ standard library compatible with various concepts of ALib is activated by including compatibility header files found in source folder alib/compatibility.
    For more information see the following namespace documentations:
  3. QT Library
    If ALib is used in combination with the QT Class Library , compatibility support for character array types (strings and character vectors) of the QT Library becomes available with modules ALib Boxing and ALib Strings.
    Such support is activated simply by including compatibility header files found in source folder alib/compatibility.
    For more information see the following namespace documentations:

7. Using The Library

There is little to say here, so we note just some bullet points:

  • A shortest C++ program was given with chapter 4. Bootstrapping And Shutting Down ALib. From there, a user of the library should start directly with the topic and module of interest.
  • Before releasing ALib, all header files are tested to be includable without the need to include a dependency header. This means, for example, if a user wants to use ALib for scanning a directory and working with file trees, the single include of header file "alib/files/fscanner.hpp" would include all necessary dependent header files.
  • When compiling code against ALib, each compilation unit needs to receive the same compiler symbols as were given to the library itself. With debug-compilations, this will be checked with the invocation of function Bootstrap.
    If different compilation symbols are used, then
  • When using ALib with Microsoft Windows OS, it may be important to include windows.h before any ALib header file. The reason is that ALib includes windows.h unless it was included already. When the ALib library includes windows.h, it uses some defines like WIN32_LEAN_AND_MEAN or NOGDI to minimize the impact of that huge header file. If windows.h is included before including ALox (AWorx library), it is up to the embedding application which level of windows functionality is needed.

Appendices

A.1 Naming Conventions

The C++ language standard does not suggest naming rules. This is different to, for example, with the JAVA language. While some people could say, that the C++ standard library naming scheme suggests such rules, others say that it is especially valuable, that code outside namespace std differs from that - to be better distinguishable within source code.

ALib uses the following rules:

  • Preprocessor symbols are "UPPER_SNAKE_CASE" and mostly are prefixed "ALIB_".
  • Global variables are also UPPER_SNAKE_CASE.
  • Otherwise, underscores (snake_case) are almost never used.
  • Namespaces are in lower case letters consisting of single words (no camel casing).
  • All other entity names are either lowerCamelCase or UpperCamelCase.
  • Public namespace functions are UpperCamelCase.
  • Hidden (e.g., anonymous) namespace functions are lowerCamelCase.
  • Types on namespace scope are UpperCamelCase.
  • Members and inner types are UpperCamelCase if public, otherwise lowerCamelCase.
  • Types and members that are available only in debug-compilations, are prefixed "Dbg".
  • Method and function parameters are lowerCamelCase.
  • Templated types UpperCamelCase with a leading 'T', for example TString. Those template types which are not aliased (like TString is aliased to String, WString, NString etc.), often are missing the leading 'T'. This is for better readability of the end-user code and also to just indicate that the direct use of the templated class is the common use. This is why, for example, class SharedVal carries no leading 'T',
    while class TSharedMonoVal does.
  • Template parameters types are also UpperCamelCase with a leading 'T'. Only in seldom cases, a template parameter is just named "T". Usually the name should indicate what kind of templated type is expected or what is to be done with it.
  • Template structs that might specialized by users of this library are UpperCamelCase with a (snake_case) prefix of "T_". For example, T_Boxer or T_CharArray.
  • Template structs that are not to be specialized by users (because they have been specialized sufficiently by ALib already) are UpperCamelCase with a prefix of "TT_". This second 'T' could be considered indicating the word "tool", as usually these are tool structs that provide a certain information about a types. Examples are found with characters::TT_IsChar, strings::TT_IsAppendable or boxing::TT_IsCustomized.
  • Methods and field-members that are available only with debug-builds, are prefixed "Dbg". Often, these members remain available in release-builds, but are empty and often static and constexpr. This way they are optimized out by the compiler, while their use does not have to be pruned using the preprocessor (i.e. macro ALIB_DBG() or symbol ALIB_DEBUG).

As a final remark, when browsing the code it could be noticed that a lot of block-formatting is used. While a line width of 100 characters is usually not exceeded, for the sake of building code blocks, sometimes this line width barrier is ignored. From our perspective, the usefulness of tabbed code blocks is very much underestimated. Besides readability, a lot of annoying typos can be detected easily, before a next build run.
In addition, tabbed code blocks often largely improve the ability to quickly refactor a codebase manually. It should be allowed to mention, that the editor of C++ IDE CLion here has some unrivalled features: Instead of only supporting block selection and copy/paste of blocks, this editor creates a set of completely new "carets" at the moment block selection starts. We want to thank the team of JetBrains for supporting ALib development by providing a free Open Source License of their absolutely superb set of IDEs.

A.2 How To Use This Library

Most of the time, a 3rd party library like ALib is used to help solving a certain task. Such tasks are addressed with higher level modules, like ALox, ALib CLI, or ALib Expressions. In this case, the corresponding module documentation should be read directly, without loosing time on learning too much about the lower level modules and types.

If you find the lower level types useful for your own projects, those will be understood over time and by reading the reference documentation of types found in their corresponding namespaces.

Should you really just be interested in general and therefore absolutely not knowing where to start reading, we recommend to start with some of the module's programming manuals, for example ALib Boxing or ALib Strings. As debug log-output is something most projects needs, another recommendation might be the to check out the tutorial of module ALox.

The manuals mentioned above may provide you with a good grasp of the design principles and features of ALib in general and thus with the value that ALib might bring to your own software projects.

Warning
Experienced C++ programmers might complain that the Programmer's Manuals are to lengthy. Together they comprise almost 500 pages!
We could now say that the reason for often being quite "verbose" is that the manual address less experienced programmers. But this is not the truth. Explaining the details just helps us to understand a module's goals better and leads to a better code design. It happens that manual sections are written before the corresponding library feature is even implemented. Or, that while writing a manual chapter, the already written code gets heavily refactored.
While we excuse for verbosity, we think that often, the reference documentation of types and entities found in the library provide a good way to avoid reading the libraries manuals! :-)

A.3 Type Aliases in Namespace ::alib

A.3.1 Why Type Aliases?

Once a C++ library becomes more complex, their types get split into different namespaces and inner namespaces. This is also true for ALib, which is split into various modules. Consequently, the common need to conveniently address all types is to add several using statements to a compilation unit. With ALib this could, for example, look like this:

 using namespace alib::threads;
 using namespace alib::strings;
 using namespace alib::expressions;

To avoid the need of the right "permutation" of using statements at the top of a users' compilation unit, ALib "mirrors" all important types into this outer namespace with type definition statements. For example, at the end of header file alib/expressions/compiler.hpp, just before closing outer namespace alib, the following statement is made:

/// Type alias in namespace \b alib.
using Compiler= expressions::Compiler;
} // namespace [alib]

With that, a single:

 using namespace alib;

fits for all ALib types and in most cases, this single using statement is all that is needed. This approach does not generate conflicts: If for example the using code would have an own class named "Compiler", it can fall back to the standard schematic, by adding just:

 using namespace alib::threads;
 using namespace alib::strings;

and accessing class Compiler explicitly as alib::expressions::Compiler.

Note
The reason why this all is possible is due to (a next great) design decision of C++. While the CODE in the following snippet: namespace A { namespace B { CODE } }
"sees" all types in namespace A, with this snippet: using namespace A::B; CODE
the CODE does not "see" types of namespace A, but only those of B.

A.3.2 Aliases of Template Types

Most of the type aliases are as simple as sampled with class Compiler above. Also templated types are often just a 1:1 alias, for example:

/// Type alias in namespace \b alib.
template<typename T, typename TAllocator= lang::HeapAllocator, typename TLock= void>
using TSharedMonoVal = monomem::TSharedMonoVal<T, TAllocator, TLock>;

But sometimes template parameters become fixed. Often (but not always) with that the name changes, i.e., the leading T is removed. As an example, take:

/// Type alias in namespace \b alib.
using SharedFTree= files::TSharedFTree<SharedLock>;

In some few situations, mostly due to technical reasons, template parameters are even changing their position. Of-course, some special care has to be taken if such types are used aliased or not. As an example, take:

/// Type alias in namespace \b alib.
template<int TEnd, int TBegin= 0, typename TInterface= int>
using BitSet = lang::TBitSet<int, TEnd, TBegin>;

Finally, often several different aliases are defined in namespace alib, providing different permutations of template types. For example, type alib::strings;TAString is aliased as:

A.4 Collecting Caller Information

A core class included in any ALib Distribution is lang::CallerInfo. It stores the following information:

  • Filename, line number, and function name received with corresponding built-in preprocessor symbols __FILE__, __LINE__ and a compiler-dependent symbol which is fetched with ALIB_CALLER_FUNC.
  • The C++ type id, fetched with keyword typeid, usually passing argument *this.
  • The C++ thread id of type std::thread::id. This is only included if compiler symbol ALIB_EXT_LIB_THREADS_AVAILABLE evaluates to true.

It is used to collect information about the caller of a function or method member. ALib collects such information mainly on two occasions:

  • For collecting information in debug-builds of the library, and
  • with module ALox to make any log output 'clickable' in a developer's IDE and to provide log "scopes" which allow enabling or disabling log statements depending on their location.

To ease the type's use, several preprocessor macros are defined. To understand their difference, it is important to be aware that, depending on the use-case, caller information is either exclusively collected in debug-builds or collected in any build-type. In the second case, it still should be the user of the library to decide whether source-code information in a release executable or not.

With this background thought in mind, the reference documentation of the following macro list should be enough to fully understand how the data flow is managed and how to efficiently use the macros.

The macros directly relate to class CallerInfo:

The following macros make use of the caller-macros:

Attention
The macros fail to work and produce compiler errors if used within static methods or namespace functions.

This is because they use keyword this to identify a caller's type. Unfortunately, the C++ language in combination with the preprocessor does not allow automatic detection of the absence of keyword this. For this reason the core macro ALIB_CALLER has to be redefined before a code unit defines functions and restored back afterward. To do this, header files alib/lang/callerinfo_functions.hpp and alib/lang/callerinfo_methods.hpp are provided. The following example demonstrates how they are to be used:

// by default, macros are activated that work within non-static methods.
// therefore this compiles
struct MyType
{
void doSomething()
{
//...do something
Log_Info("Also here, the macro ALIB_CALLER is indirectly used")
}
// but we must not define this method here
static void doSomethingStatic();
};
// now we switch to the function version:
// this way, the static method can use all derived macros:
void MyType::doSomethingStatic()
{
//...do something
Log_Info("Also here, the caller is used")
}
// Furthermore, non-members can be implemented now:
void ANamespaceFunc()
{
//...do something
Log_Info("Also here, the caller is used")
}
// Now we switch to the method version.
// This should always be done to avoid conflicts, i.e. when the order
// of header file inclusion changes, etc.

A.5 T.HPP-Files And Custom Template Instantiations

A.5.1 The Challenge and ALib's Solution

In C++, templates offer a powerful mechanism for writing generic and reusable code. But using templates extensively in header files leads to longer compilation times and potential code bloat, particularly when templates are instantiated multiple times across different translation units. The problem becomes significant in larger projects where a template may be used with many types or included in multiple source files, forcing the compiler to repeatedly generate code for the same template.

To mitigate these issues, with a few template types, ALib separates

  1. template declarations,
  2. a subset of that template's method definitions, and
  3. instantiations for combinations of template types,

into three different files. These files are:

  1. A HPP-file (.hpp) for template class declarations.
    This header is missing the definition of those methods that should not be compiled with a unit that does not instantiate a templated type with a specific combination of template parameters.
    In other words, this header is to be included by code that relies on the fact that a different compilation unit has instantiated that type already.
  2. A T.HPP-file (.t.hpp) providing the missing (and and not inlined) method definitions.
  3. A CPP-file (.cpp) that includes both, the declaration and definition files, and explicitly instantiates the template for specific, commonly used types.

This approach results in improved compilation speed, code modularity, and manageable build times while still allowing users the flexibility of creating custom instantiations when necessary.

A.5.2 Instantiations of Custom Types

With this recipe in place, it is fairly straight forward how to instantiate your custom version of an ALib-type of that sort. Let's quickly look at a sample.

For class alib::monomem::TMonoAllocator, provided with module ALib Monomem, only one built-in template instantiation is provided. This specifies the single template parameter TAllocator to be of type HeapAllocator. This instantiation is then aliased as alib::MonoAllocator.
The corresponding files are:

Let us look at the compilation unit (the CPP-file):

// #################################################################################################
// Instantiation of MonoAllocator, which is TMonoAllocator<lang::HeapAllocator>.
// #################################################################################################
#include "alib/monomem/monoallocator.t.hpp"
namespace alib::monomem { template ALIB_API class TMonoAllocator<lang::HeapAllocator>; }

That is all that the CPP-file needs to contain.

With this in mind, we can now instantiate a custom mono-allocator, one that uses a PoolAllocator as its memory source. This is done in the ALib unit-tests. Here is the excerpt:

// #################################################################################################
// Instantiation of TMonoAllocator for PoolAllocatorHA and its inner type
// detail::MAFields<PoolAllocatorHA>.
// #################################################################################################
#include "alib/monomem/poolallocator.hpp"
namespace alib::monomem {
template class TMonoAllocator<alib::PoolAllocatorHA>;
} // namespace [alib[::monomem]

With this in place, we can start using the instantiation:

// Create a pool allocator that uses heap allocation
{
// Create a mono allocator that receives its memory from the pool.
// Its growth factor is 100%, which keeps each next allocation at
// the initial size, which is a good strategy with an underlying pool!
monomem::TMonoAllocator<PoolAllocatorHA> myMono(ALIB_DBG("MyPaMa",) myPool, 1, 100);
// Allocate and construct an object.
MyType* myObject= myMono().New<MyType>(A_CHAR("Hello"));
///...
///...
// Destruct the object and free the memory
myMono().Delete(myObject);
}// destruction of the mono allocator, which passes the memory buffer back to the pool

To summarize, the fast track approach when a specific instantiation of an ALib type that splits some method definitions from the type declaration is needed, is to have a look at the compilation unit (the CPP-file), copy its code to an own compilation unit, and replace the given type with your own type.

If this type is needed in different places, create your custom header file and copy the instantiation code and add keyword extern to turn it into a declaration. This announces that the linker will find the instantiation. Such a header file has to include only the HPP-file that ALib provides, and not the "T.HPP"-file.

Attention
With WindowsOS and DLL-compilation, things may become more complicated. Here, compiler-symbol ALIB_API has to be set rightfully for the code-unit that implements an instantiation. This manual cannot go into further details of this topic. Instead general information on compiling and linking under WindowsOS has to be consulted.

A.6 Pretty Printers

For gdb (GNU Debugger), some "pretty printers" are available. Please consult page 1. Pretty Printers for GNU Debugger (gdb).