Lattice Builder Manual
Software Package for Constructing Rank-1 Lattices
Implementation Notes

Function Arguments

In Lattice Builder, there are three different types of declarations of function arguments:

void f(const T& x) // if f only reads x
void f(T& x) // if f writes to x
void f(T x) // if f needs a copy of x

Although the first prototype allows f to make a copy of x internally, it is best to leave it to the compiler to do it. For example, the following code will cause two instantiations of objects of type T:

void f(const T& x) { T y = x; y.modify(); }
...
f(T());

whereas the following allows the compiler to instantiate only one object of type T:

void f(T y) { y.modify(); }
...
f(T());

Sequence

In multiple situations, Lattice Builder needs to access arrays of values that can interchangeably be computed on-the-fly or pre-computed. The concept of sequence, in the sense of ordered list rather than infinite mathematical sequence, provides in Lattice Builder a unified interface for both. The elements of a sequence are accessed via immutable iterators (const_iterator) with the standard syntax as used for the standard C++ containers, as in the following example:

MySeq seq; // must implement begin() and end()
for (MySeq::const_iterator it = seq.begin(); it != seq.end(); ++it)
std::cout << *it << std::endl;

Or, using the new C++11 auto keyword and for syntax:

MySeq seq; // must implement begin() and end()
for (const auto& elem : seq)
std::cout << elem << std::endl;

Concrete examples are given in the tutorial in sections Sequences of Generator Values, Sequences of Lattice Definitions and Sequences of Merit Values .

See also
LatBuilder::GenSeq LatBuilder::GenSeqSeq LatBuilder::LatSeq LatBuilder::MeritSeq
Remarks
The LatBuilder::TextStream namespace provides streaming operators to iterate over sequences with a more compact syntax:
using TextStream::operator<<;
MySeq seq; // must implement begin() and end()
std::cout << seq << std::endl;
Concrete examples are given in the tutorial in section Text Output .

Bridge Sequence

The values in a sequence often need to be mapped to other values, possibly of a different type. For that purpose, Lattice Builder introduces the concept of a bridge sequence, which contains the mapped elements in the same order as the sequence it is based on.

See also
LatBuilder::BridgeSeq

Static Polymorphism

Traditional object-oriented programming makes use of dynamic polymorphism, where the exact class of an object is resolved at execution time, dynamically. It follows that polymorphic member function calls cannot be inlined, so it is often not advisable to place such a call in the core of a loop with many iterations, at the risk of cumulating inderections due to both polymorphism and function calls.

Some algorithms implemented by Lattice Builder come in several variants which require either polymorphic function calls inside such loops or a large amount of duplicated code with tiny differences. C++ allows to circumvent that problem through the use of static polymorphism, which enables compile-time resolution of the types together with member function inlining.

In some places, static polymorphism is used regardless that dynamic polymorphism could have been used without any notable performance loss, in order to maintain a consistent design. We prefer making common usage patterns of Lattice Builder's classes easier to recognize, even if that means a little bit more verbose code in places.

The most obvious example is the case of sequence classes. Their member functions begin() and end() return iterators; the type of an iterator is often specific to the type of the sequence it is pointing to.

Another example is the size parameter is represented by a different class depending on whether ordinary or embedded lattices are considered. In both cases, the class implements a numPoints() member function, that returns the number of points in the lattice, and that is accessed frequently in some parts of the code. In a traditional object-oriented approach, the two classes would derive from the same abstract base class, and would have required runtime object-type resolution at the call points of numPoints(). With the static polymorphism approach, the numPoints() function can be efficiently inlined by the compiler, thus avoiding both the overhead due to the function call and to runtime object-type resolution.

Concept

Concepts in C++ can be thought of as the static-polymorphism counterpart of dynamic-polymorphism interfaces. Like interfaces, concepts can specify requirements on the members of a class; unlike interfaces, the data types of the arguments and of the return value of a member function can differ across classes implementing the same concept. Furthermore, a concept can require type definitions (typedefs or nested classes) inside of a class. A well-known example of concept it that of iterator or of const_iterator from the STL.

Although requirements on the types are not explicited with concepts definitions in Lattice Builder, but the code is built around the idiom of concepts.

By convention in Lattice Builder, classes representing the same concept are regrouped in the same namespace.

The most important concepts are described by means of example in the Low-Level API Tutorial.

Memory Management

In Lattice Builder, when a non-trivial object is no longer needed in its scope after it is passed as an argument to a constructor or factory function, it is generally std::move()'d to the constructor or function in question. The compiler takes care of using the default move constructor on the object that is passed to the constructor and thus avoids copying the object, which can safely be stored as a class member of the `owner' object. Generator sequences (see the GenSeq namespace) are an example of non-trivial objects that are transferred to other ̀owner' objects using this mechanism. Weights objects, such as instances of LatCommon::ProductWeights or of LatCommon::ProjectionDependentWeights, are other examples of non-trivial objects, but because they are polymorphic, they are stored using a std::unique_ptr, which in turn, is passed using either std::move() or, equivalently, std::unique_ptr::release(). There are a few exceptions to that ownership policy, notably figures of merit like instances of WeightedFigureOfMerit or of CoordUniformFigureOfMerit. Some classes store references to such objects, so the objects themselves are also stored using std::unique_ptr so that references remain valid even after the object ownership has changed.

Usage of the C++11 Standard

Lattice Builder makes uses of the following C++11 features:

  • auto-typed variables
  • range-based for-loop
  • std::function and std::bind
  • rvalue references
  • move constructors and assignment operators
  • static assertions
  • extern templates
  • null pointer constant
  • strongly typed enums
  • initializer lists
  • non-static data member initializers
  • template aliases

Usage of the Boost C++ Libraries

The Lattice Builder library depends on the following Boost libraries:

In addition to these, the Lattice Builder command-line tool also depends on the following Boost libraries:

Only the Program Options library requires building and linking; the others are header-only libraries.

Usage of FFTW

The fast CBC implementation of Lattice Builder, as well as the computation of the \(R_\alpha\) criterion, depends on the FFTW library.