ALib C++ Library
Library Version: 2402 R1
Documentation generated by doxygen
Loading...
Searching...
No Matches
ALib Module BaseCamp - Programmer's Manual

Table of Contents

1. Introduction

This ALib Camp is the fundament for all other ALib Camps . An explanation to this statement is given in chapter 3. ALib Camps and Module BootCamp of the Programmer's Manual of ALib .

In addition to that, this module aggregates different sets of classes, which did not find another home and which absolutely made sense to place in this foundational ALib Camp .

As an exception from the general rules of organizing header files and namespaces, this ALib Module , respectively ALib Camp , adds files to the following sub-directories of alib/lang , namely:

These files expose their types in corresponding sub-namespaces of alib::lang, except (and that is a next exception of the rules), that the types defined with headers in alib/lang/message , expose their types directly to namespace alib::lang.

The following chapters address these four sets of header files.

2. Types Defined with Header Files in Directory "alib/lang/message"

As just explained, the following types defined in headers and compilation units of alib/lang/message are exposed in namespace alib::lang. This is because they are very fundamental and provide general ALib functionality.

2.1. Class Message

When measured in source code, the size of class Message is very short. Nevertheless, as the type leverages almost all features of module ALib Boxing , it is a quite powerful tool to have.

The type acts as an information object of the central throwable type Exception , discussed in the next chapter. While class Exception stores a whole list of messages, only one single object is used with class Report , which is discussed in the final chapter of this small manual.

2.1.1 Source Location

Objects of type Message relate to a source code location, which is reflected by fields File , Line and Function .

The inclusion of the Function, which is of-course a redundant piece of information, already indicates that this source information has a general notion of "debug information" and at least is supposed to be presented to experts only.
Interface methods that create a Message object and for this expect the caller's source information, are usually invoked using a preprocessor macro to provide the three arguments. There are two different macros defined for this purpose, ALIB_CALLER and ALIB_CALLER_NULLED. The first macro provides the corresponding built-in preprocessor symbols for the source file, line number and currently compiled method/function name in any compilation configuration. The second macro is equal to the first only in debug-compilations. In release (or other non-debug) builds, macro ALIB_CALLER_NULLED provides nulled values for the arguments.

It depends on the using software which macro should be used. It must be noted, that with the use of macro ALIB_CALLER, even in release compilations, information about the name and location of C++ source code files and method names is included in the data segment of the executable or library built. Hence this is rather a "political" or "commercial" question to answer. Internally, ALib exclusively uses the "pruning" version ALIB_CALLER_NULLED. This is due to the fact that otherwise a using software's executable would include source information of ALib which may not be wanted.

Note
There is a third macro existing with ALIB_CALLER_PRUNED, which is empty with non-debug compilations. This macro is to be used with code that completely omits source information parameters in release compilations, which is not true for class Message and therefore not applicable with the creation of Message objects.

2.1.2 Message Types

To define the type of a message, class Enum provided by module ALib Boxing is used, by letting field Message::Type be of type Enum.

This leads to having a two-level hierarchy for defining message types. What is meant by this, should be explained by looking at other options:

  • If field Type was of integral type, it would be no hierarchy, but just a "flat" numbering system. With that it would be very problematic to find a numbering scheme that allows software modules that do not "know of each other" to define message types without ambiguity.
  • If the field was of a virtual base type, it would be a hierarchy of arbitrary depth: A message type class might be derived from the virtual base class, which in turn has derived types. Now, when investigating into a message's type, a user could try to dynamically cast the base type to a certain derived message type and on success would know that the message is of that type or a derived type.

By using class Enum , there are exactly two hierarchy levels: the first is accessed with method Enum::IsType . With that it can be determined if the "boxed" enumeration element is of a certain scoped enum type. To check both levels at once, Enum::operator== can be used.

Note
  • While this is not recommended in common use cases, method Enum::Integral allows to to test only the second level of the hierarchy, which is the underlying enum element's integral value.
  • It is also not recommended to use non-scoped (anonymous) enumeration types. While two elements of two anonymous enum types are well distinguished with Enum::operator== , the use of Enum::IsType is rather tricky and not well readable: the declaration type of a sample element has to be given as the template parameter to the method.

2.1.3 Message Arguments

The contents of the message is comprised by a list of boxed objects. For this, class Message inherits class Boxes , which itself is a container (vector) of objects of type Box . The interpretation of the data given in a message is defined by an application. As described in a later section of this manual, ALib class Exception, which includes a whole list of Message objects, proposes a certain schematic for the attached data. But also in this case, the proposal is just a recommendation, not a mandatory requirement.

2.1.4 Memory Allocation

As documented with class Boxes , its underlying std::vector may use either standard dynamic memory or "monotonic allocation" introduced by module ALib Monomem . Such choice is also exposed with class Message by offering two constructors with the only difference that one takes a MonoAllocator in addition to the arguments that the other constructor expects.

This way, instances of type Message may be used as "stand alone" objects (with usual dynamic memory allocation) or might be embedded in other objects that may - if using monotonic allocation - not only allocate the message itself in memory chunks, but also have the message's arguments using that same allocator.

Note
Later in this manual, class Exception is discussed, which carries a whole list of messages that share one monotonic allocator.

2.1.5 Argument Life-Cycle

Restricted life-cycles of a message's arguments is a potential pitfall. Because class Message is a vector of boxes (by inheritance), for non-trivial arguments, a pointer to the corresponding original object instance is stored.

Note
For details on how a value of a certain type is "boxed" when added as an argument to class Message, wider parts of the programming manual of module ALib Boxing have to be understood. As a rule of thumb: objects that have a bigger size than 2 x 64 bit (respectively 2 x 32 bit on a 32-bit system) will not be copied, but a pointer to the original is stored.

Especially in the case of C++ exception handling (what the type is used for), this means that boxed objects must not be stack-allocated, because the stack is "unwinded" when the exception is thrown.
Therefore, (complex) stack-allocated objects and all other objects that might not survive the life-cycle of the message, need to be copied if they should be used as a message argument.

One solution for this and similar cases is already offered by module ALib Boxing with built-in box-function FClone . This function expects an object of type MonoAllocator that is used to allocate memory for cloning the contents of boxed data.

See also
For more information, see the reference documentation of box-function FClone and chapter 12.6 Life-Cycle Considerations of the Programmer's Manual of module ALib Boxing .

In the case that the Message object itself uses monotonic allocation for the internal vector (see previous section), parameterless method Message::CloneArguments may be used, which is a simple inline that passes the internal allocator to inherited method Boxes::CloneAll .

2.2. Exceptions

2.2.1 Exception Handling Paradigms

Class Exception is used within ALib as the one and only type thrown with C++ keyword throw.

Different paradigms or "design pattern" exists for the C++ language, proposing how to implement exception handling. There is a lot of discussion about this going on in the community and different paradigms have different advantages. For example, one alternative is to throw one exception object and while the stack is unwinded, this original exception gets replaced (by intermediate exception handlers) with a more meaningful one. This replacement object might be derived from the original one or belong to a different exception hierarchy. For example, if a resource string can not be loaded, a type of "resource exception" may be thrown. Then, the caller of this method has different information, for example that it can not access a server (because the server name was meant to be read from the resource) and now replaces the resource-related throwable with a server-access-related one.

ALib adheres to a different pattern and class Exception supports, respectively implements this. An exception once thrown is never replaced. Instead, while unwinding the stack new "entries" are added to the exception. The entries are of type Message, introduced in the first chapter of this manual.

Now, a new entry can have two effects:

  • Either the new message is "overwriting" the meaning of an exception and this way changing the overall exception code,
  • or the new message extends the existing meaning of an exception by adding detail information to the exception. Such detail information is generally information that only the "caller" of a method that caused the exception can add from it's context.

While this sounds complicated, it is not: the whole class is not much more than a list of objects of type Message and the exception information passed with the constructor of the class, simply becomes the first entry in the list. Each new message, regardless if the message changes the meaning of the exception or extends it with detail information, is simply appended to the list of entries. The exception just keeps track of the most recent entry that changed the meaning, while the overall order of message entries is always kept intact.

With this approach, all information "along the call stack" is preserved in the order of the information occurrence.

This allows:

  • Exception handling code to inspect "older" entries and perform different actions depending on the original cause.
  • Exception output that gives more meaningful information about what happened exactly, e.g. during software development or when logging exceptions "from the field" for later analysis.

Just to be clear: class Exception is not designed to be a base class for derived exception types. The recommended use of the class is that the type itself is the exclusive throwable. Consequently, when catching ALib exceptions, only one catch block is needed (possible) and within this catch block code might to perform different actions depending on the exception entries.

Of-course, a user of the library might break this rule and derive own exception types, but it is really not recommended. Think about it first! No ALib Module uses derived types and only this type needs to be "caught" when invoking ALib code that throws.

2.2.2 Exception Types

With the paradigm/design pattern implemented (as discussed in the previous introductory section), usually an important problem is raised: What can be used as an "error type" and how can it be assured that error types defined in independent code units of a software are not ambiguous? Remember that the more common approach to implement exceptions, which is also taken in the C++ standard library that defines class std::exception to be a general base class for exceptions, such ambiguities do not occur: There, the C++ type system is used to distinguish exceptions. With the ALib implementation however, the entries need to use a "type-code" and these codes must not overlap.

This problem is solved by using type Message as entries of class Exception . As discussed in previous chapter 2.1.2 Message Types, this way a two-level hierarchy of exception types is established, where the first level uses run-time type information and hence no clash of exception types may occur, even between different code units.

There is one question left: Which of the messages in the collected list now does define the resulting "exception type"? Is it the first message added? Or the last one? In the paradigm ALib uses it might be any of them, or even one in-between.

The exception type is defined as: "The type of an exception is defined by the most recently added message, who's enum element's integral value is positive."

This definition is implemented with interface method Exception::Type . The method loops from the oldest to the newest message entry and searches the last one with a positive integral value in field Message::Type .

With this definition, enumeration element values of exception message entries that are considered "additional information" are to be declared with a negative integral initializer, while those that constitute an own exception type, are to be declared with a positive value.

Consequently, if the a catch handler uses method Exception::Type it does not need to switch over just all possible entry types (aka enumeration elements), but just over those that are positive and that therefore bring a different meaning to an exception.

2.2.3 Self-Contained Allocation

When an exception is thrown, something "bad" happened. In that situation a software should not perform too many complex things, for example dynamic memory allocations.

Simple exception types are therefore very lightweight, as for example class std::runtime_error which contains a simple, dynamically allocated string message.

The exception type introduced here seems in contrast rather heavy! It allows to collect many new entries and each entry, as being of type Message in turn may consist of a bigger collection of message arguments.

To minimize the number of heap allocations, class Exception implements a quite tricky approach. For all allocations (entries and their arguments of type Box ), an object of type MonoAllocator is used. We had learned in chapter 2.1.5 Argument Life-Cycle that such allocator is used by the messages and thus this part of efficient allocations is solved already.

Secondly, class MonoAllocator allows to locate itself in a first block that it allocates. Thirdly, while this self-contained monotonic allocator is a member of class exception, the true implementation of the class is then still allocated using the monotonic allocator! The astonishing result is, that the footprint (sizeof) class Exception is just one simple pointer! Hence, when the exception is passed (by the C++ compiler) to a next "stack frame" while unwinding the stack, only this simple pointer is copied.

In addition, unless the attachment of many messages and data exceeds the initial allocated chunk of memory, only one single dynamic memory allocation is performed for storing the exception itself along with it's data.

Each time an entry is added with method Exception::Add , a new message object is allocated in the monotonic allocator and the values of the given boxes are cloned (see previous chapter 2.1.5 Argument Life-Cycle).

The only operation that the destructor of class Exception performs is to delete the allocated memory chunks. This is because none of the contained objects needs further destruction calls. Neither the boxed objects, nor the vector of such, nor the monotonically allocated list of message entries.

As a result, the rather complex type Exception becomes extremely lightweight. While it has the same minimum footprint as exceptions found in the standard C++ library, it usually also performs only one single memory allocation to store all information collected while unwinding the stack.

2.2.4 Exception Entries And Arguments

We learned so far that class Exception uses a list of entries of type Message , discussed in the first chapter of this manual.

Besides the source information and the message types, there is a third aspect that class Exception leverages from its entry type Message : the list of arbitrary arguments of arbitrary length.

But what are the message arguments used for in exceptions and what arguments should be added to exception entries? The answer is that this is completely use-case specific. For example, the provided arguments might help to implement an exception handler that mitigates the situation by being enabled to analyse in detail what happened.
In parallel, usually a human readable textual description of the exception is desired.

To have just both - a human readable textual description and computational values representing the state that caused the exception - it is recommended to adhere to the following simple scheme for the arbitrary argument list: The first argument should be a string which comprises a format string containing placeholders and following a syntax usable with ALib Formatters (or custom formatter syntax implementations). Then this string is followed by the "computational" arguments that comprise valuable information about the state of the software when an exception was thrown.

Of-course, all exceptions thrown by ALib itself adheres to this scheme. To support the generation of human readable exception descriptions, a ready to use formatting feature is given with overloaded methods Exception::Format .
In addition, when using the debug- and release-logging facilities provided with module ALox , exceptions can be comfortably logged with static method LogTools::Exception . In the latter case, during development, a very helpful and comfortable feature is that each exception entry collected during unwinding the call stack is "clickable" in an IDE's output window, so that the source code that created an entry can be displayed and analysed right away.

Note, that both formatting options (the built in method and ALox ) are respecting the agreement of message entry types having either positive or negative values. As explained above, this agreement is constituted by method Exception::Type that searches the most recent enty with a positive enum element value. "Informational entries" are marked as such in the output.

Considering the message arguments to be doubly used, once while formatting an exception description and once while analysing the cause of an exception, a small conflict may arise: If a catch handler may need some information that should not be used with the description. In this case, a corresponding "suppression placeholder" may be added to the format string. For example, using the python-style formatter (FormatterPythonStyle ); placeholder {!X} would suppress an argument completely. (Note: This is an extension to the original python formatter syntax.)

2.2.5 Catching Exceptions, Adding Information, Rethrowing

While objects of type Exception is extremely lightweight, consisting only of a single pointer (remember that all information is stored in an object of type MonoAllocator , including not only the monotonic allocator itself, but also the whole exception type itself!), they are not copy-constructible.

Therefore, they must be caught only by reference:

     catch( alib::Exception& e )
     {
        ...
     }

In the case an exception should be rethrown, method Exception::Add may be used to add one or more new entries to the exception instance. Method Add has the very same signature as the constructor of the exception.

For the same reasons that exceptions have to be caught by reference, rethrowing has to. The following code does not compile:

   catch( alib::Exception& e )
   {
      throw e.Add( ... );
   }

Here is the right way to do it:

   catch( alib::Exception& e )
   {
      e.Add( ... );
      throw;
   }

2.2.6 Resourced Exceptions

To enable the external maintenance of the description strings of exception entries (as explained in previous chapters, it is proposed and recommended to add a descriptive, human readable string as a first argument to each exception entry), this module uses the concept of ALib Enum Records. This concept in turn allows the definition of records be performed using externalized string resources.

When custom enum types are associated with ALib Enum Records of type ERException , elements of this type become accepted by method Exception::Add which internally adds the resourced description string.

Please consult the reference documentation provided with the links in this section for further information.

2.3. Reports

What today is called "ALib Reports" is a rather simple concept of raising C++ assertions, similar warnings and general messages, currently all of them in debug-compilations only.

The concept is implemented with types:

Furthermore, module ALox introduces plug-in

The concept is not very advanced and might undergo changes in the future. Consequently, we do not recommend the use of Reports in custom code outside of ALib and therefore, apart from the reference documentation of the classes listed above, no Programmer's Manual exists.

3. Types Defined with Header Files in Directory "alib/lang/resources"

3.1. Introduction

ALib uses the term "resources" for string data that technically may be defined in a constant and static fashion, but that a software volunteers to make configurable. Typical samples of data that a software exposes for external management are "themes" ( color and font schemes), or so called "externalized strings", which mostly may be used to translate a software into a different "locale" and human language.

While the conceptual definition of resources is very similar to the concept of configuration data, it is good practice to thoroughly decide for each specific piece of data that is deemed to be made configurable, whether it is resource or configuration data. While the mentioned samples of language translation strings and color schemes are typically resources, a server name that a software attaches to is typically configuration data. End users commonly know and understand the difference between the two intuitively and appreciate if the corresponding data sources are separated, especially as this way, resource data does not "clutter" the definitions of configuration sources.

The best rule of thumb to differentiate resource from configuration data is to check if it is possible to distribute a software without turning the data in question into either of the two externalization concepts, thus "hard coding" the data. If so, the data more likely is resource data. In the sample of the "server address" above, this is not possible if such server address targets a machine that is under the control of an end user. However, if it is a machine provided by the distributor of the software which is the same with any installation, then the concept of resourced data rather fits.

Note
The concept of configuration data is implemented with module ALib Configuration .

3.2. Resources and Modules

As documented in chapter 2. ALib Modules of the ALib Programmer's Manual, the various ALib Modules can be separated into those who are ALib Camps and those which are not. In this module dependency graph it could be noted that all ALib Camps (dark blue) are dependent on module ALib BaseCamp , discussed in this manual.

This relation is explained as follows:

  • All high-level ALib modules need externalized string resources (e.g. for ALib Exceptions, ALib Enum Records, etc.).
  • Resources need a controlled process of bootstrapping a software.
  • Library class Camp holds a pointer to an object of type ResourcePool .
  • This class furthermore provides strictly defined and phased bootstrapping mechanics, especially designed to support the customization of resource management.

Therefore, all ALib Camps not only depend on module ALib BaseCamp , but also dispose about a singleton type derived from core library class Camp.

3.3. Data Contract / Conceptual Invariants

ALib resources implemented with this namespace, conceptually impose a set of rules which may be named a "contract", "invariants" or just "determinations". These rules are given in the following subchapters.

3.3.1 Resource Keys

Key values to resources are defined as a two level hierarchy of a category and a name. This determination complies with how configuration data is addressed with module ALib Configuration . This is also in alignment with common 3rd-party resource management systems established in the industry.

Independent from the compilation options of ALib in respect to choosing the default character width, the string type of the category and name is fixed to NString , hence using the narrow nchar type. This is not in alignment with configuration data as defined in ALib Configuration . The rational for this is that while configuration data categories and names may be translated themselves to localized versions (preferably by defining those strings as resources!), the category and name strings of the resources are deemed to be hard-coded in the source code that accesses resources. As such, they are not shared with end-users, are never localized and should be using the plain 7-bit printable ASCII character set.

3.3.2 Restricted To String Type

All resource data is of String type (of compilation-dependent character width).

This restriction imposes that any other data type (for example color codes) has to be de-serialized (decoded) when resourced.

3.3.3 Static Data

Although resource data technically is non static data, conceptually with this implementation it is.

This determination has the following impacts:

  • Resources do not have to be "acquired" and "released" when accessed, which tremendously simplifies the use of resources.
  • A using code can rely on the fact that the life-cycle of the string buffers of accessed resources is infinite. No local copies of the string data is required. (Note, that due to the missing option of acquisition/release of resources, copying a resource string would not even be possible.)
  • Resource data does not dynamically change during the life-cycle of a software process. In other words, accessing resource data of a specific category and name, will always result in the same returned value.
  • Consequently, an implementation of abstract interface type ResourcePool that attaches to a 3rd-party resource system which supports dynamic resources, usually has to create a copy of the data returned from the 3rd-party system and return this copy instead of the original value.
Note
The latter point is implemented with built-in, alternative resource type ConfigResourcePool , which relies on module ALib Configuration to maintain the resources externally. Configuration data implemented with this module is not static and thus may change during the life-cycle of a software. Consequently, this type uses monotonic allocation with type HashTable to store persistent copies of each resource, at the moment it is accessed the first time. With subsequent requests, the same cached data is returned.

3.3.4 Thread Safeness

Accessing resources using abstract interface method ResourcePool::Get is a thread-safe operation.

In contrast to this, an invocation to any of the methods that define resources, namely ResourcePool::Bootstrap and ResourcePool::BootstrapBulk is not a thread-safe operation. This is true in respect to each other as well - and most important - also in respect to parallel resource access with method Get .

This determination has the following impacts:

  • Interface methods prefixed with the term Bootstrap of abstract type ResourcePool are deemed to exclusively being invoked during bootstraping of ALib as well as the of the according software using ALib . Such bootstrapping has to be performed prior to starting any threads that potentially modify resources.
  • Interface method Get of abstract type ResourcePool may be called in parallel from different execution threads, without the need of locking a specified mutex .
  • An implementation of interface ResourcePool may need to impose an internal protection in respect to race conditions of multi-threaded access.
Note
Class LocalResourcePool , which is used by default if bootstrapping of ALib is not customized accordingly, is not in the need of using an internal mutex. This arises from the fact that after the single-threaded bootstrap finished, only read-operations on the resource pool are performed. The type just does not provide any other feature.
In contrast, class ConfigResourcePool , which relies on module ALib Configuration to manage the resources, takes internal precautions against race conditions of multi-threaded access, because configuration data may change during the life-cycle of a software. Changes are imposed by the (custom) plug-ins that are attached to its configuration object or by other code entities that are allowed to access the public configuration object directly.

3.4. Interface Class ResourcePool

The central type of the module, class ResourcePool was already mentioned several times. It constitutes a pure abstract interface. Due to the determinations of the concept given in previous chapter 3. Data Contract / Conceptual Invariants, its interface is very simple especially in respect to accessing resources, which is exclusively done with method ResourcePool::Get .

A user of ALib should have no bigger effort to implement this interface and adopt her own or any 3rd-party "backend" that performs the resource management and the externalization of strings.

Apart from that, two implementations of the interface are provided with ALib . Those are quickly introduced in the following sections.

3.4.1 Class LocalResourcePool

As explained above, an implementation of interface ResourcePool has to be constructed during bootstrap of ALib and distributed among the modules.

In case the bootstrap process is not customized, an instance of class LocalResourcePool is created and shared.

This class does not allow any externalization of resources and simply stores the given pointers to the static data in a HashTable , using monotonic allocation.

3.4.2 Class ConfigResourcePool

A second built-in implementation of class ResourcePool which can be created and shared among the modules of ALib by customizing the bootstrap process of the library, is given with class ConfigResourcePool .

The type externalizes resources by using entities found with module ALib Configuration . For this, it disposes about a public member of type Configuration and installs one InMemoryPlugin with default priority . This plug-in fetches all resources fed with methods Bootstrap and BootstrapBulk .

If after creation of an instance of the type, this instance is not changed and just used, then it behaves in an identical way as type LocalResourcePool (with only weaker performance and increased memory overhead).

Externalization is achieved when a custom plug-in is attached to the configuration object found with field ConfigResourcePool::Config . For example, this could be of type IniFile and be attached with priority Priorities::Standard .

With that, any entries made in that INI-file, would simply overwrite the corresponding resource values which use the lower default priority.

With this type a very simple but yet powerful way of externalizing resource data is given. The type is suitable in any situation where no other ("more professional") 3rd-party "backend" for resource management is available.
Here are some tips for the usage:

  • Method Configuration::FetchFromDefault may be used to create (for example) an INI-file that contains all resources that have been defined during bootstrap of a software.
  • Such INI-file could then be used as a "jump-start" for translating a software from its (hard-coded ) default language into a different one.
  • With the prioritization mechanics of class Configuration it is allowed to attach two or more INI-files, in parallel. This allows for example to have language translations of resources in place, which - if only sparsely defined - automatically fallback through the different configuration plug-ins until a translation is found, or - if none is found - fall back to the defaults stored in the InMemoryPlugin and initially fed with ResourcePool::BootstrapBulk .
    In other words, language translations can be stacked and software might allow to use not only specify a preferred language but a prioritized list of preferred languages.
  • Similar to the previous point two different configuration plug-ins (e.g. INI-files) could be used and have one storing only translated resources, the second all resources relevant for an application's color scheme ("Theme"). This way, language and color scheme can be chosen independency from each other.
  • Finally, this class also provides an alternative to implementing an own version of interface class ResourcePool from scratch. Precisely, instead of implementing interface ResourcePool, a may implement interface ConfigurationPlugin and attach this to field ConfigResourcePool::Config .
    One of the advantages of this approach would be that not all resource data needs to reside in the attached "backend". Some data might also reside in a different backend (with a next plug-in), or again in an INI-file.
Note
Despite its flexibility, type ConfigResourcePool is provided for convenience. It was just a "low hanging fruit" of implementation making use of sibling module ALib Configuration .
There are no plans on the road map of ALib to impose a more sophisticated implementation for externalized string resources. It is simply not in the domain of this library to provide a higher level of functionality. The whole purpose of this module itself ist to have an abstract and very simple interface into any sort of "resources backend" and its choice is completely independent from the choice of using ALib with a software!

3.5. Indirect Resource Access

3.5.1 TMP Struct T_Resourced

Sometimes it is required to define resource information, namely

  • the ResourcePool instance,
  • the resource category and
  • the resource name

for use by other components. TMP struct T_Resourced may be specialized to do such definition for C++ types. A specialization of the struct can be easily implemented using macro ALIB_RESOURCED.

A sample for the use of this struct is given with module ALib Configuration : To load and store configuration data, this module exposes a type of ALib Enum Records and accepts custom enumeration types in various interface methods, if they just have this specific record type associated.

Now, if an element of a custom enumeration type that disposes about a specialization of T_Resourced is passed to such interface method, internally this information is used to load further details of the variable from the resource pool.

3.5.2 Helper Struct Resourced

As soon as struct T_Resourced is specialized for a type, helper struct static struct ResourcedType becomes available.

The type has two overloaded methods Get : The first is parameterless and simply receives the resource string associated to a type with the specialization of T_Resourced. The second takes a replacement for the resource name. This may be used to retrieve resource strings which are likewise associated to the type.

Furthermore, the struct provides methods TypeNamePrefix and TypeNamePostfix which are meant to provide a standardized way to define a type's name using resources. The methods are for example used with specializations T_Append<TEnum,TChar> and T_Append<TEnumBitwise,TChar> which write enum element names into instances of type AString .

3.5.3 Helper Struct ResourceInfo

A next helper struct is given with ResourceInfo which first of all is a simple struct that stores resourcing information (the resource pool and category and name strings) for later use.

While this struct is usable without a specialization of T_Resourced , in most use cases it is, because it allows to convert the compile-time information given by T_Resourced into run-time information.

3.6. Further Details

3.6.1 Resourced Data Records And Tables

Besides just externalizing strings, many use cases require to access externalized data sets or even whole tables of this.

ALib module ALib Enums provides a solution for this with its concept ALib Enum Records. There a table of data is addressed using the C++ type information of enum types. Single records of a table may (or may not) be addressed by elements of the corresponding enumeration. The module provides convenient facilities the fields of the records and whole tables from resourced strings.

Before you go ahead and implement your "own" solution for externalized static data, it might be worthwhile to check out if ALib Enum Records meet your requirements.

3.6.2 Exporting Resources For Externalization

When resources are externalized, for example for translation to different human languages, the list of resources have to be imported to the external "backend". To do so all resources have to be queried from the library.

Here is a sample program that performs this task:

using namespace alib;
using namespace std;
int main( int argc, const char *argv[] )
{
// create and set resource pool that uses a configuration file
alib::Camps.Back()->BootstrapSetResourcePool( &pool );
// bootstrap alib
alib::ArgC = argc;
alib::ArgVN= argv;
// we externalize the string value, e.g. replacing "\" by "\\" and this way "\n" by "\\n"
// This might not be wanted for custom exports, but works well for ALib INI-files.
config::XTernalizer externalizer;
AString externalizedValue;
// loop over sections of the plugin
auto& sections= pool.Config.GetPluginTypeSafe<InMemoryPlugin>(
Priorities::DefaultValues )
->Sections();
for( auto& section : sections )
{
// no entries in section? (happens only for first, empty category)
if( section.Entries().IsEmpty() )
continue;
// write category
cout << endl << '[' << section.Name() << ']' << endl;
// loop over resources in actual category
for( auto& entry : section.Entries() )
{
// externalize and write
externalizer.ExternalizeValue( entry.Value, externalizedValue.Reset() , 0 );
cout << entry.Name() << '=' << externalizedValue << endl;
}
}
// shutdown alib
return 0;
}

The output of this sample can directly be loaded by class IniFile , hence with a plug-in attached to an instance of built-in resource pool implementation ConfigResourcePool . The sample might be adopted accordingly to write a format suitable to be imported to the backend of choice.

With every update of the library, changes of the resource strings have to be determined. This might be done for example with a custom unit test that compares the default entries with the ones currently stored in an external backend. New resources might be added, others might disappear, and worst: existing ones might change their content format. In the latter case, an externalized resource might be errorneous and lead to undefined behavior.

Starting with library version 1903, to support the detection of changes, the version history is found in

    ALIB_BASE_DIR/docs/pages/resource-exports/

The following exports are available:

3.6.3 Debug- And Usage Statistics

With the provision of compiler symbol ALIB_DEBUG_RESOURCES, static field LocalResourcePool::DbgResourceLoadObserver becomes available. If set to &std::cout prior to bootstrapping ALib , the resource load process can be observed on the console, because methods LocalResourcePool::BootstrapBulk and LocalResourcePool::BootstrapAddOrReplace will write information on bulk and singular resource data definitions. This tremendously helps to find errors in resource strings, which often are simply missing commas and similar.

Next, virtual method ResourcePool::DbgGetCategories and ResourcePool::DbgGetList become available. The latter returns a list of all resources defined, including a usage counter. The method is only implemented by class LocalResourcePool . Alternative type ConfigResourcePool does not implement it.

Furthermore, convenience method ResourcePool::DbgDump becomes available which simply writes the list of symbols into an AString, sorted by category and in an alphabetical order.

The usage counter included in the list may be used to identify two groups of resource strings:

  • Resource strings that never have been loaded, and
  • Resource strings that have been loaded very often during the actual execution of a software.

While entries of the first type may have become obsolete and are candidates for removal, those of the second type, a software might consider to "cache" the symbol in a variable instead of repeatedly retrieving it from the resource pool.

Remember: While trivial implementation class LocalResourcePool is very fast and resource access is not really noticeable, other implementations might not be.

The following code snippets taken from the ALib unit tests, demonstrate how to quickly leverage these debug features, depending on compiler symbol ALIB_DEBUG_RESOURCES. The snippets might be copied to own projects and remain there forever.

Bootstrapping may look as follows:

#if ALIB_DEBUG_RESOURCES
#endif
int main( int argc, char **argv )
{
ArgC= argc;
ArgVN= const_cast<const char**>(argv);
#if ALIB_DEBUG_RESOURCES
resources::LocalResourcePool::DbgResourceLoadObserver= &std::cout;
#endif
//...
//...

Before termination of a software (hence, prior to invoking Shutdown ), the following code may be placed:

#if ALIB_DEBUG_RESOURCES && ALIB_ALOX
Log_Info( "---------------- Resource Pool Dump ----------------" )
auto categoryList= alib::BASECAMP.GetResourcePool().DbgGetCategories();
integer sum= 0;
for( auto& category : categoryList )
{
Log_Info( "Resource category {:10} has {:3} entries", category.first, category.second )
sum+= category.second;
}
Log_Info( "This sums up to ", sum, " resource definitions" )
auto resourceList= alib::BASECAMP.GetResourcePool().DbgGetList();
Log_Info( ResourcePool::DbgDump( resourceList ) )
Log_Info( "---------------- Resource Pool Dump (end) ----------" )
#endif

3.7. Sample

A comprehensive sample of using ALib resources placed in a custom module is provided with the tutorial of ALib Module 'CLI'. The sample code provided there, can be easily used as a jump start into an own project that creates a custom ALib module and leverages the resource features provided.

4. Types Defined with Header Files in Directory "alib/lang/format"

4.1. Introduction

This ALib Module provides string formatting facilities by implementing an approach that is common to many programming languages and libraries. This approach offers an interface that includes the use of a "format string" containing placeholders. Besides this format string, a list of data values can be given, used to fill the placeholders.

Probably one of best known samples of such an interface is the printf method of the C Language. A variation of this interface is found in almost any high-level, general purpose programing language.

Of-course, this module leverages module ALib Strings for all general string functions needed. Similar important is the use of module ALib Boxing , which brings type-safe variadic argument lists and allows with its feature of having "virtual functions" on boxed arguments, to have custom formatting syntax for placeholders of custom argument type.

While it is possible to implement a formatter providing a custom placeholder syntax, two very prominent ones are built-in with formatters:

  • FormatterJavaStyle
    Implements the syntax provided with the formatter included with the core class libraries of the JAVA programming language. This syntax is an extension of the good old printf format string style.
  • FormatterPythonStyle
    Implements the syntax provided with the formatter included with the core class libraries of the Python programming language. This syntax is very powerful and flexible in respect to the provision of syntax extensions for custom types.
    Over time, this formatting syntax became the preferred syntax within ALib itself and we have extended the syntax even in some respects in comparison to the original definition.

Another good news is that in its very basics, Python Style is similar to .Net formatting. This way, there is some "familiar basic syntax" available for everybody that has used formatting in one of the languages C, C++, C#, Java or Python and in languages that have also mimicked one of these styles!

4.2. Using The Formatters

By leveraging module ALib Boxing , which implies the use of variadic template arguments, the invocation of the final format method is as simple as it is possible. The following samples a simple format action with each of the two built-in formatters:

AString target;
FormatterJavaStyle() .Format( target, "The result is %s!\n", 6 * 7 );
FormatterPythonStyle().Format( target, "The result is {}!\n", 6 * 7 );
cout << target;

This produces the following result:

The result is 42!
The result is 42!

Values of or pointers to any type that is "boxable" may be passed as an argument to method Formatter::Format . The specific implementation of the formatter will match the "placeholder type" with the given argument type and format the argument according to the placeholder attributes.

4.2.1 Concatenated Formatters

In the sample above, two different formatters are created and each is used "properly", namely with its according syntax.
To increase flexibility, the formatters of this ALib Module provide two features:

  • Formatters can be concatenated
  • Formatters detect format strings and on failure, pass processing to concatenated formatter.

With that information, the following code can be written:

AString target;
// create two formatters and concatenate them
formatter.Next.reset( new FormatterPythonStyle() );
// both formats string syntax versions may be used now the first formatter.
formatter.Format( target, "%s style\n", "Java" );
formatter.Format( target, "{} style\n", "Python" );
cout << target;
Note
While the first formatter is a simple local object (stack allocated), the second formatter is created on the heap (keyword new) then stored in field Next of the first formatter. This field is of type SPFormatter , which is an alias for std::shared_ptr<Formatter>, hence a C++ standard "smart pointer" that deletes it's contained object automatically with the deletion of the last referrer. Only in later chapters it will be explained why it is the preferred method to manage ALib formatter instances in shared pointers.

The short sample code correctly produces the following output:

Java style
Python style

However, the placeholder syntax must not be mixed within one format string. Let's try:

formatter.Format( target, "---%s---{}---", "Java", "Python" );

The output is:

---Java---{}---Python


This is obviously not what we wanted, but then it also did not produce an exception and it even included the second argument, "Python" in the output. While exceptions are discussed in a later chapter only, the reason that no exception is thrown here is simply explained: The first formatter in the chain, which we defined as type FormatterJavaStyle , identified the format string by reading "%s". It then "consumes" this string along with as many subsequent arguments as placeholders are found in the format string. This number is just one, as the placeholder "{}" is not recognized by this formatter.

The intermediate result consequently is "---Java---{}---", while the argument "Python" remains unprocessed. The next section explains what happens with this remaining argument.

4.2.2 Concatenating Format Operations

We just continue with the sample of the previous section: The unprocessed argument "Python" is not dropped, as it would have been with most implementations of a similar format functions in other libraries and programming languages. Instead, with ALib BaseCamp , the formatting process starts all over again using the remaining argument as the format string.

Now, as it is not a format string (it does not contain placeholders in any known syntax) it is just appended to the target string "as is".

Note
For users who are familiar with modules ALib Boxing and ALib Strings : The words "appending as is", here means, that the remaining argument is appended to the target string in a type-specific way. Because all arguments are of the same type, namely Box , this in turn means that box-function FAppend is invoked on the box, which just performs the type-dependent string conversion.

In fact, for this last operation, none of the two formatters became active. The trick here is that the abstract base class, Formatter already implements method Format. This implementation loops over all arguments. It checks if the current first argument is a string recognized as a format string by any of the chained formatters. If it is not, this argument is just appended to the target string and the loop continues with the next argument in the list.
If a format string is identified, control is passed to the corresponding formatter that consumes as many further arguments as placeholders are found in that format string, and then passes control back to the main loop.

Consequently, this approach allows to invoke Format without even a format string:

formatter.Format( target, 1,2,3 );
123

which probably does not make any sense, because the same result could have been achieved much more efficiently by stating:

target << 1 << 2 << 3;

Even the following sample, still might not make too much sense to a reader:

formatter.Format( target, "--- A: {} ---", 1, "--- B: {} ---", 2 );

because the usual way to come to the same result, was to have only one format string with two arguments, instead of two format strings with one argument each:

formatter.Format( target, "--- A: {} ------ B: {} ---", 1, 2 );

So, why is this loop implemented with it's auto-detection and the option of having more than one format string? Some sound rational for the loop is given in the next section.

4.2.3 Decoupled Format Argument Collection

Method Format collects the variadic template arguments to an internally allocated container of type Boxes . This container is then passed to the internal format loop.

Alternatively, the collection of format arguments in a container object may be performed "manually" by the user code. In this case, the formatter is invoked with one of the two overloaded methods Formatter::FormatArgs . Both methods must be invoked only after an explicit call to Formatter::Acquire . One of the two methods does not accept an external container and instead operates on the internally allocated instance which method Acquire returns. The second function allows to pass an arbitrary external container instance, for example one of derived type Message .

With this knowledge it becomes obvious, that the collection of formatting arguments can be "decoupled" from the invocation of the formatter. Note that the argument list may include zero, one or even multiple format strings, which each are followed by corresponding placeholder values:

AString target;
Boxes& results= formatter.Acquire(ALIB_CALLER_PRUNED);
results.Add( "The results are\n" );
// calculating speed
//...
//...
results.Add( " Speed: {} m/s\n", 42 );
// calculating mass
//...
//...
results.Add( " Mass: {} kg\n", 75.0 );
// calculating energy
//...
//...
results.Add( " Energy: {} Joule\n", 66150 );
try
{
formatter.FormatArgs( target, results );
}
catch( Exception& e )
{
e.Format( target );
}
formatter.Release();
cout << target << endl;
The results are
Speed: 42 m/s
Mass: 75.0 kg
Energy: 66150 Joule

A reader might think for herself if and when this might become useful. It should be noted that the interface of logging module ALox builds on the same mechanism. The arguments there are called "logables" and might be format strings or anything else. Therefore, also with ALox the collection of log entry data can be decoupled from the final creation of the log entry. This is especially useful for complex log-entries whose arguments are collected during the execution of an algorithm and for example are only logged in case of an exception or other unexpected conditions.

4.2.4 Default Formatters

In the previous samples, a local instance of a formatter (or two) has been created. For general purpose use, this module provides a global pair of (concatenated) formatters which are receivable with static methods Formatter::GetDefault and Formatter::AcquireDefault .

The formatter returned is embedded in "smart pointer type" SPFormatter . During bootstrapping of the library, a formatter of type FormatterPythonStyle is created with a concatenated object of FormatterJavaStyle .

One obvious rational for the provision of these default formatters is of-course to save memory and processing resources by reusing the formatter instances in different parts of an application. However, probably in most cases more important is the fact that this way, the same default configuration is used with formatting operations. For example, if the decimal point character of floating point numbers should be defaulted to be different than US/English standard '.', then such setting could be performed with the bootstrap of the library once and for all usages across a process.

4.2.5 Cloning Formatters

Formatter implementations may or may not provide default settings that for example influence a format operation that uses minimal placeholders that omit optional formatting flags. The built-in formatters do have such default settings.

If a software unit wishes to change some settings, the advised approach is as follows:

  • Retrieve the default formatter(s)
  • Create a clone of the default formatter(s) by invoking Formatter::Clone .
  • Change the default settings of the cloned formatter.
  • Use the cloned formatter.

With this procedure, any changes that an application applied to the default formatters (e.g. during bootstrap), will remain valid in the cloned formatters in addition to the "local changes", while the default formatters remain untouched.

For example, built-in formatters provide in fields DefaultNumberFormat and AlternativeNumberFormat to reflect some default behavior of their formatting syntax.
The attributes of these members might be modified to change those defaults. While this leads to a deviation of the formatting standard, it may be used instead of providing corresponding syntactic information within the placeholder field of each and every format string. Some modifications may not even be possible with the given format specification syntax.

4.2.6 Exceptions

The simple samples shown so far used correct format strings. In case of errorneous format strings, the built-in formatters will throw an ALib Exception defined with enumeration alib::lang::format::FMTExceptions.

While in the case of "hard-coded" format strings, such exceptions are not needed to be caught, their evaluation (with debug builds) might be very helpful for identifying what is wrong. Of-course, when format strings are not hard-coded but instead can be provided by the users of a software (for example in configuration files or command line parameters), a try/catch block around formatting invocations is a mandatory thing, also in release compilations.

The following sample shows how an exception can be caught and its description may be written to the standard output:

#if ALIB_DEBUG
try
{
#endif
AString target;
alib::Formatter::GetDefault()->Format(target, "Unknown syntax: {X}", "Test");
cout << target;
#if ALIB_DEBUG
}
catch(Exception& e)
{
cout << e.Format();
}
#endif

The output of running this code is:

E1: <format::MissingClosingBracket>
Closing bracket '}' of placeholder not found (or syntax error).
In: "Unknown syntax: {X}"
^

In most cases, a detailed text message, including a copy of the format string and a "caret" symbol '^' that hints to the parsing error in the string is given with the exception's description.

4.2.7 Escape Sequences In Format Strings

Escape characters, like for example "\t", "\n" or "\\" might be given with either one or two backslashes. The formatters will convert them to the corresponding ASCII code, if the backslash itself is escaped.

Class FormatterPythonStyle recognizes double curly braces "{{" and "}}" and converts them to a single brace. Similar to this, class FormatterJavaStyle recognizes "%%" and converts it to a single percentage symbol.

4.3. Formatting Custom Types

As we have seen, the use of module ALib Boxing allows the formatters of this module to accept any third-party data type as formatting arguments. The formatters of-course are enabled to "convert" all C++ fundamental types to strings. But how about custom types?

The solution for custom conversion is given with the support of "box-functions", which implement a sort of "virtual function call" on boxed types.
There are two box-functions that the built-in formatters are using.

4.3.1 Box-Function FAppend

By default, the very simple box-function that is used by the built-in formatters for converting arbitrary types to string values, is FAppend . This function is one of the built-in functions of module ALib Boxing and this way is not specific to this module ALib BaseCamp .

Usually this function's implementation just unboxes the corresponding type and appends the object to the target string.
Let as look at an example. The following struct stores a temperature in Kelvin:

struct Kelvin
{
double value;
};

If an object of this class is used with a formatter without any further preparation, the default implementation of function FAppend is invoked, which writes the memory address of the given object. In debug-compilations, this implementation in addition writes the boxed type's name (platform dependent and implemented with class DbgTypeDemangler ). This is shown in the following code and output snippet:

Kelvin temperature { 287.65 };
AString target;
Formatter::GetDefault()->Format(target, "The temperature is {}\n", temperature);
cout << target;
The temperature is Kelvin(Size: 8 bytes)

The first step to implement function FAppend for sample type Kelvin is to specialize functor T_Append for the type:

target << Format(src.value - 273.15, &nf) << " \u2103"; // Degree Celsius symbol (small circle + letter 'C')
)

With that in place, it is possible to apply an object of this type to an AString:

Kelvin temperature { 287.65 };
AString target;
target << temperature;
cout << target << endl;
14.5 ℃

Now, we can easily implement box-function FAppend, because for types that are "appendable" already, this is done with just a simple macro that has to be placed in the bootstrap section of a software:

With that in place, it is possible to append a boxed object of this type to an AString:

Kelvin temperature { 287.65 };
AString target;
Box temperatureBoxed= temperature;
target << temperatureBoxed;
cout << target << endl;
14.5 ℃

Because the formatters use the same technique with the boxed arguments they receive, our sample class can now already be used with formatters:

Kelvin temperature { 287.65 };
AString target;
Formatter::GetDefault()->Format(target, "The temperature is {}", temperature);
cout << target << endl;
The temperature is 14.5 ℃

To summarize this section, some bullet points should be given:

  • Independently from this module ALib BaseCamp and the formatters defined here, class AString provides a concept based on template meta programming that allows to append objects of arbitrary type to strings.
  • With the availability of module ALib Boxing , a box-function named FAppend is established that is invoked in the moment an instance of class Box is appended to an AString.
  • By defining a specialized version of this function for a custom type, boxed values of the custom type can be appended to an AString.
  • The formatter classes provided with this module, use this function with custom types.
  • Consequently, if a custom type has already been made compatible with both modules, ALib Strings and ALib Boxing , no special preparations have to be made to use the type with the formatter classes.

4.3.2 Box-Function FFormat

The previous section demonstrated how a custom type can be made "compatible" to ALib formatters found in this module ALib BaseCamp .

The given approach using box-function FAppend is quite limited in that respect, that within a format string no attributes might be given that determine how to format a custom type. With the sampled temperature type "Kelvin", the output format was in celsius with one decimal digits. If we wanted to allow Fahrenheit as an alternative output, we need to implement boxing function FFormat , which was specifically created for this purpose and consequently is defined in this module.

Note
Type-specific format specification strings are allowed only with the Python-like syntax of format strings. The Java-like formatter does not provide a feature of "embedding" custom format specifications in the format string.

The function has three parameters: besides the box that it is invoked on and the target string to write to, it receives a string that provides type-specific information about how the contents is to be written. This format specification is fully type and implementation specific and has to be documented with the specific function's documentation.

We want to implement a format string that starts with character 'C', 'F' or 'K' to specify celsius, fahrenheit or kelvin and a following integral number that specifies the fractional digits of the output.

To do this, the following function declaration needs to go to a header file:

void FFormat_Kelvin( const Box& box, const String& formatSpecGiven, NumberFormat& nf, AString& target );

Then, the implementation of the function has to be placed in a compilation unit. This might look like this:

void FFormat_Kelvin( const Box& box, const String& formatSpecGiven, NumberFormat& nf, AString& target )
{
// set default format spec (in real code, this should be using a resourced default string)
String formatSpec= formatSpecGiven.IsNotEmpty() ? formatSpecGiven
: A_CHAR("C2");
// get value from boxed object
double value= box.Unbox<Kelvin>().value;
// get precision
Substring precisionString= formatSpec.Substring(1);
if( precisionString.IsNotEmpty() )
{
int8_t precision;
precisionString.ConsumeDec( precision );
nf.FractionalPartWidth= precision;
}
else
// convert unit (or don't)
String unit= A_CHAR("\u212A");
if( formatSpec.CharAtStart() == 'C' )
{
unit= A_CHAR("\u2103");
value= value - 273.15;
}
else if( formatSpec.CharAtStart() == 'F' )
{
unit= A_CHAR("\u2109");
value= value * 1.8 - 459.67;
}
// write value
target << Format( value, &nf) << ' ' << unit;
}

Within the bootstrap section of the process, the function has to be registered with ALib Boxing :

// This lock is usually NOT NEEDED!
// We do this, here because this sample code is run in the unit tests, when ALib is already
// bootstrapped.
// See note in reference documentation of function BootstrapRegister()
alib::boxing::TMappedTo<Kelvin> >( FFormat_Kelvin );

With that in place, we can use the custom format specification with our custom type

Kelvin temperature { 287.65 };
AString target;
Formatter::GetDefault()->Format(target, "The temperature is {:C2}\n", temperature);
Formatter::GetDefault()->Format(target, "The temperature is {:F0}\n", temperature);
Formatter::GetDefault()->Format(target, "The temperature is {:K5}\n", temperature);
cout << target;

The following output is produced.

The temperature is 14.5 ℃
The temperature is 58 ℉
The temperature is 287.65 K

As a second sample we want to look at the internal implementation of formatting date and time values. ALib class CalendarDateTime provides (native) method Format to write time and date values in a human readable and customizable way. This method also requires a format specification. Now, this helper class is used to implement FFormat for boxed arguments of type DateTime , which is given with class FFormat_DateTime . Due to the existence of the helper class, the implementation of the function is therefore rather simple:

void FFormat_DateTime( const Box& box, const String& formatSpec, NumberFormat&, AString& target )
{
tct.Format( formatSpec.IsNotEmpty() ? formatSpec
: BASECAMP.GetResource("DFMT"),
target );
}

4.4. Custom Formatters

To implement a custom formatter that uses a custom format string syntax, no detailed manual or step-by-step sample is given here. Instead just some hints as bullet points:

  • The typical use-case for implementing a custom format string is to mimic an existing formatter of a different programing language or different C++ library, to be able to reuse the formatting strings, which might be resourced and shared between different implementations of a software.
  • The built-in formatters both use "intermediate" class FormatterStdImpl as a base class. This class might be used for custom formatters as well, as it already implements a skeleton that has to be completed by implementing a set of specific abstract methods.
  • It is recommended to review (and copy) the sources of one of the given formatter implementations. While FormatterPythonStyle is by far the more powerful implementation, class FormatterJavaStyle might be less complicated to start with.
  • A thorough understanding of modules ALib Boxing and ALib Strings is a precondition for the implementation of a custom formatter.

4.5. Further Types Provided By This Module

Besides the formatter classes that have been discussed in this Programmer's Manual, module ALib BaseCamp provides some other types which make use of the formatters.

As of today, these types are

Please consult the class's extensive reference documentation for more information about the features and use of these types.

5. Types Defined with Header Files in Directory "alib/lang/system"

This sub-namespace of ALib BaseCamp provides types that interface into the operating system of the host computer. The types found here are just a very few and this namespace is not meant as being anything of huge value for third party applications. Rather, the types have been implemented when other parts of ALib needed corresponding functionality.

That being said, we refer the reader to the reference documentation.