Utilities

The conversion utilities of the Platinum core library include functions to convert byte orders, to format and parse numbers, and to perform checked numeric conversions.

Basic application support is provided by the Pt::Arg class and the Pt::Settings class. The first one is a convenient way to parse and process program options and the latter one allows to load and store application settings in files or other places.

Two types are useful to get type information Pt::TypeTraits and Pt::TypeInfo. TypeTraits are used for generic programming, for example to deduce the pointer type in templated code, or to branch differently for const and non-const types. The TypeInfo class is a wrapper for std::type_info, which makes it easier to store and compare type information. The std::type_info is normally not copyable and comparable. Pt::SourceInfo is used to store information about a location in the source code.

String Conversions

The framework includes functions for fast conversion between strings and numbers. The overloaded functions parseInt() and formatInt() convert between strings and integers, and parseFloat() and formatFloat() convert between strings and floats. The functions work with iterators as input or output instead of string objects, so they can be used with simple buffers or even streams, as shown in the following example:

#include <Pt/Convert.h>
#incldue <iterator>
#inlcude <iostream>
std::ostream_iterator<char> it(std::cout);
Pt::formatInt(it, 42);
const char* buf = "42";
const char* bufend = buf + 2;
int n = 0;
Pt::parseInt(buf, bufend, n);

A stream iterator is used to format a number directly to std::cout and then a number is parsed from a raw character buffer. Floating point numbers can be formatted and parsed in a similar way like integers:

#include <Pt/Convert.h>
#incldue <iterator>
#inlcude <iostream>
std::ostream_iterator<char> it(std::cout);
Pt::formatFloat(it, 42.123);
const char* buf = "42.123";
const char* bufend = buf + 6;
int f = 0;
Pt::parseFloat(buf, bufend, f);

By default, decimal format will be used when numbers are parsed and formatted, but overloads exist that accept an additional format object. A few format objects are already provided by the framwork, named Pt::DecimalFormat, Pt::OctalFormat, Pt::HexFormat or Pt::BinaryFormat, which allow numeric conversion in a different base. The next example shows how integers in hex format can be parsed and formatted:

#include <Pt/Convert.h>
#incldue <iterator>
#include <iostream>
Pt::HexFormat<char> fmt;
std::ostream_iterator<char> it(std::cout);
Pt::formatInt(it, 0x42, fmt);
const char* buf = "0x42";
const char* bufend = buf + 4;
int n = 0;
Pt::parseInt(buf, bufend, n, fmt);

All parse functions used so far throw an exception of type Pt::ConversionError, if the conversion failed. Overloads of the parse functions are available, which set a bool flag instead, to indicate a conversion error. Both, the format and parse functions return an iterator pointing to the position after the last character that was written or read, respectively. Partial consumption of the input is not treated as an error.

Numeric Conversions

Numeric assignments can lead to loss of data, if the operation narrows the data type to a smaller one. For example, numeric conversion from int to short can be an error, if the assigned value exceeds the maximum or minimum value possible for shorts. The narrow() function can be used instead of normal assignment, to protect against this. In case of an error, an exception of type Pt::ConversionError is thrown.

#include <Pt/Convert.h>
#include <limits>
#include <iostream>
long l = ...;
short s = 0;
try
{
s = Pt::narrow<short>(l);
}
catch(const Pt::ConversionError& e)
{
std::cerr << "numeric value is out of range: " << l << std::endl;
}

Byte Order Conversion

The byteorder conversion API consists of two sets of functions. The swab() function is able to swap the byteorder of a type by bytewise copying as shown in the following example:

#include <Pt/Byteorder.h>
Pt::uint32_t value = ...;
Pt::uint32_t swapped = Pt::swab(value);

The swab() function is overloaded for all fixed-size integer types such as Pt::uint32_t. A second set of functions can be used to convert from a specific external byteorder to the native host byteorder:

For example the function beToHost() converts from big-endian to the host byte order:

#include <Pt/Byteorder.h>
Pt::uint32_t beVal = ...;
Pt::uint32_t value = Pt::beToHost(beVal);

Command Line Arguments

Arg objects can be used to process command line options passed to the main function of the program. A syntax for short-named and long-named options is supported. Short-named options start with a single hypen ('-') followed by a single character and optionally a value. Long-named options start with two hyphens followed by string and optionally a value.

The template parameter of the Arg class is the argument value type, which must be streamable, i.e. the operator >> (std::istream&, T&) must be defined for the type T. When an Arg is constructed, the operator will be used to extract the value from the command-line string. the next example demonstrates this:

int main(int argc, char* argv[])
{
Pt::Arg<int> option_n(argc, argv, 'n', 0);
std::cout << "value for -n: " << option_n << endl;
}

Options are removed from the option-list, so programs can easily check, if there are parameters left, after all options were extracted. A specialization exists for boolean parameters. This implements a switch, which is on, if the option is present and off, if it is missing. The option consists, in this case, only of a command line flag without a value. Boolean parameters can also be grouped, so -abc is processed like -a -b -b.

Pt::Arg<bool> debug(argc, argv, "debug");
if (debug)
std::cout << "debug-mode is set" << std::endl;

The example shown above not only shows a boolean parameter, but also how long-named options are handled, in this case "--debug".

Application Settings

Many programs need to be able to restore its settings from a persistent location, such as a file. The Settings class provides an hierachical organisation of settings entries and an API to read and write them in a text format. The following example illustrates how settings can be read from a file:

std::ifstream ifs("app.settings");
Pt::TextIStream tis(ifs, new Pt::Utf8codec);
Pt::Settings settings;
settings.load(tis);

Settings can be loaded from any input stream, so the API is not limited to files. In this example, a file stream is opened and a text input stream is used to read UTF-8 encoded text. Another interesting use-case is to load settings from a string stream, which can greatly simplify unit testing. Writing settings to a file is just as easy:

std::ofstream ofs("app.settings", std::ios::out|std::ios::trunc);
Pt::TextOStream tos(ofs, new Pt::Utf8codec);
Pt::Settings settings;
settings.save(tos);

Any output stream can be used to save the settings, in this case UTF-8 encoded text is written to a file. Note, that the file is truncated when opened, so the content is replaced.

Settings are saved in a compact text format, which supports integers, floats, strings and booleans as scalar value types and arrays and structs as compound types. The next example shows some possibilities:

a = 1
b = 3.14
c = "Hello World!"
d = true
e = [ 1, 2, 3 ]
f = { red = 255, green = 0, blue = 0 }

The entry values for a, b, c and d are of type integer, float, string and bool, respectively. The entries e and f demonstrate the syntax for arrays and structs. The following example shows how such a settings file can be loaded and how the entries are accessed:

std::ifstream ifs("app.settings");
Pt::TextIStream tis(ifs, new Pt::Utf8codec);
Pt::Settings settings;
settings.load(tis);
int a = 0;
bool ok = settings["a"].get(a);
float b = 0;
ok = settings.entry("b").get(b);
ok = settings.entry("c").get(c);
bool d = false;
ok = settings.entry("d").get(d);
std::vector<int> e;
ok = settings.entry("e").get(e);
Color f;
ok = settings.entry("f").get(f);

The entry() method or alternatively, the index operator can be used, to access entries and subentries by name. If a subentry does not exist, an empty entry object will be returned. Values can be retrieved with the get() method, which returns false, if the value does not exist. The data type, which is stored in the settings must be serializable i.e. the serialization operators must be defined. The framework defines the serialization operators for STL containers, so these work out of the box. The set() function can be used to set an entry to a new value, before the modified settings are saved. New subentries can be added using the addEntry() function.

Settings can be split into sections, to improve the readability of the file, using the following syntax:

[animals]
a = "dog"
b = "cat"
[plants]
a = "tulip"
b = "rose"

When such a settings file is loaded, it will contain two entries named "animals" and "plants". Both entries will have two subentries named "a" and "b".

Source Information

The SourceInfo class is used to store information about a location in the source code. This includes the file name, the line number and the name of the function. The PT_SOURCEINFO macro can be used to construct a SourceInfo object conveniently, based on compiler macros such as __FILE__ or __LINE__.

Pt::SourceInfo si(PT_SOURCEINFO);
// print file, line and function
std::cout << si.file() << std::endl;
std::cout << si.line() << std::endl;
std::cout << si.func() << std::endl;

Note, that the SourceInfo will contain the file and line information of the location where the PT_SOURCEINFO macro is used.

Type Information

The normal std::type_info class is not copyable, so only raw pointers can be stored in containers such as std::vector. The TypeInfo class addresses this problem, by wrapping std::type_info into a type with value semantics. It also adds comparison operators, like less-than comparison, which allow to use it as the key for assoziative containers:

// OK, TypeInfo is copyable
std::vector<Pt::TypeInfo> typeVector;
// OK, TypeInfo is less-than comparable
std::map<Pt::TypeInfo, std::string> typeMap;