2.6.0
Freundlich's C++ toolkit
Coding style

Typing

A (mostly) statically typed language like C++ has the advantage to forbid programs to even run if they are not well-typed. Making the most out of the type system can catch a lot of mistakes at compile-time. We can use types to

Total functions

A total function is a function that is well-defined for every possible combination of parameters. A partial function on the other hand can be undefined for some. This includes everything that is not expressed by the type system: undefined behavior, aborting the program or even throwing an exception. An example of a partial function is dereferencing a pointer. The pointer might be a null pointer, it might be uninitialized or it might point to 'one past the end'. Calling partial functions is error-prone because the programmer has to remember for which parameters the function is undefined. Consequently, partial functions are best avoided but cannot always be:

See fcppt.assert on how to systematically handle partial functions.

Loops

Loops can be broken down into two separate concerns: Updating the loop condition and updating the loop's result. For example, a loop that sums integers from 1 to n-1 can be written as:

int result{0};
for(int i{1}; i < n; ++i) result += i;

Here, updating the loop condition is done by incrementing i and updating the result by result += i. Instead of the loop condition we can use fcppt::int_range. Updating the loop's result can be decoupled from the loop itself using an algorithm, in this case fcppt::algorithm::fold.

0,
[](int i, int result) { return i + result; }
)

Unlike iterators, ranges have the great advantage of being composable, for example two ranges can be joined (taking their product) or appended, which is implemented by the Boost.Range library. For a collection of algorithms that replace loops see fcppt.algorithm. Because imperative loops, especially ones using iterators, are so error-prone, raw loops are best avoided.

Const and initialization

Declaring an object const makes it immutable and therefore easier to reason about. Obviously, a const object has to be initialized directly which can sometimes be hard to express. Consider an array of arbitrary size in a template:

template<typename T, std::size_t N>
std::array<T,N> make_array();

This case is handled by fcppt::algorithm::array_init, for example:

return fcppt::algorithm::array_init<std::array<T,N>>(
[](std::size_t i) { return from_int<T>(i); });

Another common case is container initialization which usually consists of a series of push_back or insert operations. This is handled by fcppt::algorithm::map.

Initialization also brings up the point of default constructors, which for historical reasons all fundamental types have. Instead of int i{0}; the default constructor int i{}; can be used. This is like pulling a value out of thin air and makes code less readable.

Default constructors become worse when classes are more complex.

Alternatives to the classes mentioned above are fcppt::unique_ptr, fcppt::function and fcppt::variant::object, which do not provide default constructors.