Modern C++ Design
Modern C++ Design: Generic Programming and Design Patterns Applied is a seminal book on advanced C++ programming techniques, authored by Andrei Alexandrescu and published in 2001 by Addison-Wesley.[1] The work focuses on leveraging C++ templates and generic programming to implement design patterns in a highly flexible and reusable manner, introducing concepts such as generic components—reusable design templates that generate boilerplate code directly for the compiler.[2] It combines elements of object-oriented programming, template metaprogramming, and classic design patterns to enable more expressive and extensible software designs.[3]
The book emphasizes policy-based design, where components can be customized through interchangeable policies, allowing developers to tailor functionality without altering core code structures.[2] Key techniques covered include partial template specialization for type-safe operations, typelists for manipulating collections of types at compile time, and implementations of patterns such as Visitor, Singleton, Command, and Abstract Factory using templates.[2] Alexandrescu also explores multi-method engines and other advanced features, demonstrating how to create libraries that adapt to user needs while maintaining efficiency.[4]
Accompanying the book is the Loki library, a C++ template library that provides ready-to-use implementations of the discussed patterns and utilities, making the concepts immediately applicable in projects.[2] Loki's comprehensive use of metaprogramming became a benchmark for C++ compiler conformance, with the phrase "compiles all of Loki" serving as an informal test of compiler robustness and influencing vendor improvements.[4]
Modern C++ Design has significantly shaped modern C++ practices, coining the term "modern C++" to describe idiomatic uses of templates and generics that paved the way for later standards like C++11 and beyond.[4] Endorsed by prominent figures like Herb Sutter, it remains a foundational resource for understanding template-heavy design, despite some code examples being constrained by the compilers available at the time of publication.[2]
Book Overview
Publication History
Modern C++ Design: Generic Programming and Design Patterns Applied was published on February 13, 2001, by Addison-Wesley Professional.[1] The book carries the ISBN 0-201-70431-5.[1]
Upon release, the book received acclaim as a seminal contribution to generic programming in C++, building on the foundations established by the C++98 standard.[5] It demonstrated innovative uses of template metaprogramming to implement extensible design patterns, influencing subsequent developments in the language's ecosystem.[5]
No major revised editions have been issued, though multiple printings occurred, with the 17th printing dated February 2009.[6] Errata and code updates for the accompanying Loki library are provided through the author's website.[7]
The work predates the C++11 standard and emphasizes template metaprogramming techniques available in pre-C++11 compilers.[5]
Author Background
Andrei Alexandrescu was born in 1969 in Bucharest, Romania. He earned a BSc in Electrical Engineering from the Politehnica University of Bucharest in 1994. In 1998, he immigrated to the United States, where he initially worked in the financial and dot-com sectors while pursuing advanced studies in computer science.[8]
Alexandrescu's interest in advanced C++ techniques developed during this period, leading him to contribute to the C/C++ Users Journal as a columnist on generic programming and template metaprogramming. His work was significantly influenced by Alexander Stepanov's pioneering efforts in generic programming, as seen in the Standard Template Library (STL), and by the design patterns framework outlined in Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides' seminal book Design Patterns: Elements of Reusable Object-Oriented Software. Scott Meyers, author of influential C++ texts such as Effective C++, played a key role as Alexandrescu's mentor, encouraging him to expand his journal articles into a full book and recommending him to publisher Addison-Wesley.[9][4][4]
These experiences culminated in Alexandrescu's authorship of Modern C++ Design: Generic Programming and Design Patterns Applied, published in 2001, which applied template metaprogramming to implement flexible design patterns. Following this, he completed a PhD in Computer Science at the University of Washington in 2009 and co-authored C++ Coding Standards: 101 Rules, Guidelines, and Best Practices with Herb Sutter in 2005, earning the Jolt Productivity and Innovation Award. Alexandrescu later became a research scientist at Facebook and co-designed the D programming language, further establishing his expertise in systems design.[8][10][8]
Core Themes
Modern C++ Design emphasizes the creation of generic components, which are reusable design templates that automatically generate boilerplate code for compiler consumption, enabling developers to build expressive and flexible software within standard C++ without external tools.[1] This approach unites established design patterns from object-oriented programming with the power of C++ template metaprogramming, allowing for highly customizable components that adapt to specific needs at compile time.[1]
Central to the book's philosophy is the rejection of "do-it-all" classes that attempt to encapsulate every possible behavior, as such designs often lead to inflexible and bloated code.[11] Instead, it advocates for composable policies—small, interchangeable template parameters—that can be combined modularly, offering greater flexibility than traditional inheritance hierarchies while minimizing coupling and promoting reuse.[1] Policy-based design emerges as a key idiom embodying this principle, facilitating the infusion of behaviors into classes through templates.[1]
The structure reflects this focus: Part I (Chapters 1–4) introduces foundational techniques for implementing generic components, while Part II (Chapters 5–11) demonstrates their application to advanced design patterns.[11] Overall, the work aims to empower compile-time customization that eliminates runtime overhead, resulting in performant, maintainable systems tailored precisely to requirements.[1]
Fundamental Techniques
Policy-Based Design
Policy-based design is a template-driven idiom that decomposes complex classes into smaller, interchangeable components known as policies, which encapsulate specific behaviors or structural aspects of the class. These policies are integrated as template parameters into a host class, allowing customization at compile time without altering the core implementation. This approach promotes flexibility by enabling developers to select and combine policies to tailor the class's functionality for different requirements.
The primary benefits of policy-based design include orthogonality and compile-time customization. Orthogonality ensures that policies remain independent of one another, facilitating a mix-and-match composition that avoids unintended interactions between features. Compile-time customization binds behaviors statically during compilation, resulting in optimized code with no runtime overhead for policy selection or switching. This contrasts with dynamic alternatives by leveraging C++ templates to generate specialized instantiations, enhancing reusability and performance in generic programming scenarios.
A foundational example of policy-based design is the CriticalSection class, which manages thread synchronization using a ThreadingModel policy to define locking strategies. The host class template is structured as template <class ThreadingModel> class CriticalSection, where the policy class provides methods like lock() and unlock(). For instance, a single-threaded policy might implement no-op operations for simplicity, while a multi-threaded policy could employ mutexes with optimizations such as double-checked locking to minimize contention.
cpp
template <class ThreadingModel>
class CriticalSection {
public:
void enter() { policy_.lock(); }
void leave() { policy_.unlock(); }
private:
ThreadingModel policy_; // Instance for stateful policies like mutex
};
template <class ThreadingModel>
class CriticalSection {
public:
void enter() { policy_.lock(); }
void leave() { policy_.unlock(); }
private:
ThreadingModel policy_; // Instance for stateful policies like mutex
};
In this setup, instantiating CriticalSection<SingleThreaded> yields a lightweight, non-synchronizing version, whereas CriticalSection<DoubleCheckedLocking> incorporates thread-safe mechanisms with reduced overhead. Policies must adhere to a consistent interface to ensure seamless integration.
Policy-based design shares conceptual similarities with the Strategy design pattern, which also allows interchangeable algorithms for behavior variation. However, while Strategy relies on runtime polymorphism through virtual functions and pointers, incurring dispatch overhead, policy-based design resolves selections at compile time via template instantiation, achieving zero runtime cost and inlined optimizations. This makes it particularly suitable for performance-critical applications where behavior is known statically.
Typelists
Typelists are a foundational metaprogramming construct introduced in Andrei Alexandrescu's Modern C++ Design, serving as compile-time linked lists that enable manipulation of type collections through templates.[12] They address the absence of built-in language support for type sequences by providing operations analogous to those on runtime value lists, such as construction, traversal, and modification, all resolved at compile time.[1] This allows developers to encode and process sets of types statically, reducing boilerplate and enhancing genericity in pattern implementations like factories and visitors.[12]
The basic structure of a typelist is a binary template Typelist<Head, Tail>, where Head is a single type and Tail is another typelist, recursing until termination with NullType, an empty type marker.[13] For instance, a typelist containing int, float, and double is expressed as Typelist<int, Typelist<float, Typelist<double, NullType>>>.[12] This recursive definition facilitates linear construction and supports operations like appending a type or typelist to the end, erasing a specific type (with options for the first or all occurrences), and indexed access to retrieve a type by position.[13] These operations rely on template partial specializations and recursion, ensuring type safety without runtime overhead.[1]
Key operations are implemented as metafunctions. For length calculation, the Length template uses recursion:
cpp
template <class TList> struct Length;
template <> struct Length<NullType>
{
enum { value = 0 };
};
template <class T, class U> struct Length<Typelist<T, U> >
{
enum { value = 1 + Length<U>::value };
};
template <class TList> struct Length;
template <> struct Length<NullType>
{
enum { value = 0 };
};
template <class T, class U> struct Length<Typelist<T, U> >
{
enum { value = 1 + Length<U>::value };
};
This yields the number of types in the list at compile time, with Length<Typelist<int, Typelist<float, NullType>>>::value evaluating to 2.[12] Indexed access, via TypeAt<TList, N>, traverses the list linearly to return the Nth type, specialized for NullType to handle out-of-bounds gracefully.[13] Append combines two typelists by recursing through the first to attach the second, while erase removes matching types through recursive filtering.[12]
Typelists provide utility in enabling type-safe handling of variant-like structures, where a discriminated union can be generated from a typelist to support compile-time-checked type dispatching.[1] They also facilitate policy selection by allowing compile-time iteration over type options to compose behaviors.[12] In policy-based design, typelists briefly serve as a mechanism to enumerate and combine multiple policy types into a single class template.[1]
A notable limitation of typelists in pre-C++11 environments is their reliance on recursive template instantiations, which can exceed compiler-imposed depth limits (typically 256-1024 levels, depending on the implementation), restricting practical typelist sizes and complicating large-scale metaprogramming.[12] This recursion also leads to linear compile-time complexity for operations like indexing, potentially increasing compilation times for deep lists.[13]
Small-Object Allocation
Frequent allocations of small objects in C++ programs, such as those used in runtime polymorphism, the pimpl idiom, or components like functors and smart pointers, often lead to significant performance degradation and memory fragmentation when relying on the default free store allocator. The standard new and delete operators incur substantial overhead for objects of a few bytes to tens of bytes, including metadata storage that can inflate memory usage by 50% to 400% (e.g., for an 8-byte object) and result in allocation speeds up to 10 times slower than larger allocations due to heap management costs and poor locality.
To address these issues, Andrei Alexandrescu introduces a small-object allocator in Modern C++ Design that optimizes memory management for objects up to 256 bytes by allocating from pre-reserved fixed-size pools rather than the general heap.[14] The core component is the SmallObjAllocator class, which dispatches requests to specialized fixed-size allocators based on object size, falling back to the global operator new for larger objects to maintain compatibility. This approach reduces fragmentation by grouping similar-sized objects and improves cache locality through contiguous storage in chunks, achieving allocation speeds comparable to stack allocation while minimizing overhead. Typelists are employed to define supported chunk sizes at compile time, enabling type-safe management of multiple allocation categories.[15]
The allocator's flexibility is provided through policy-based design, allowing customization of parameters such as chunk size (defaulting to 4096 bytes), maximum small-object size (default 256 bytes), alignment requirements, and threading models for concurrent access. Chunk allocation strategies vary between fixed and variable approaches: in the fixed strategy, memory within a chunk is divided into uniform blocks linked via a singly linked list of free blocks (capped at 255 per chunk to fit metadata), with new chunks allocated dynamically as needed; variable strategies adapt block sizes more fluidly but at higher complexity. These policies ensure the allocator can be tuned for specific workloads, balancing speed, memory efficiency, and thread safety.
A representative implementation in the accompanying Loki library uses a singleton allocator templated on parameters including chunk size and max small-object size, as shown below (simplified):
cpp
template
<class ThreadingModel = LOKI_DEFAULT_THREADING_NO_OBJ_LEVEL,
std::size_t chunkSize = LOKI_DEFAULT_CHUNK_SIZE,
std::size_t maxSmallObjectSize = LOKI_MAX_SMALL_OBJECT_SIZE>
class AllocatorSingleton : public SmallObjAllocator {
// Manages pools for fixed-size allocations
public:
static void* Allocate(std::size_t size);
static void Deallocate(void* p, std::size_t size);
// Chunk management implementation
};
template
<class ThreadingModel = LOKI_DEFAULT_THREADING_NO_OBJ_LEVEL,
std::size_t chunkSize = LOKI_DEFAULT_CHUNK_SIZE,
std::size_t maxSmallObjectSize = LOKI_MAX_SMALL_OBJECT_SIZE>
class AllocatorSingleton : public SmallObjAllocator {
// Manages pools for fixed-size allocations
public:
static void* Allocate(std::size_t size);
static void Deallocate(void* p, std::size_t size);
// Chunk management implementation
};
Classes intended for small-object allocation derive from a SmallObject base that overloads operator new and operator delete to route through this allocator, enabling seamless integration with smart pointers for automatic deallocation without altering ownership semantics. This technique is particularly effective in the Loki library, where it boosts performance for frequently instantiated small components.[14]
Advanced Patterns and Applications
Generalized Functors
In Chapter 5 of Modern C++ Design, Andrei Alexandrescu introduces generalized functors as a technique to create type-erased callable objects that encapsulate arbitrary invocations, such as free functions, member functions, and functor objects, thereby extending functionality similar to what would later become std::function in C++11 but adapted to pre-C++11 constraints.[16] The primary goal is to enable decoupled interobject communication through the Command design pattern, allowing clients to store and invoke requests as first-class, type-safe objects without exposing the underlying callable's specific type.[16] This approach supports member functions by binding an object instance and a pointer to the method, as well as functor-like behaviors through templated constructors that adapt lambda-equivalent objects (i.e., ad hoc functor classes) via template specialization.[16]
The implementation centers on a Functor class template, parameterized by the return type R and a typelist TList representing argument types (using the typelist technique from earlier chapters).[16] For zero-argument cases, a specialized template <class R> class Functor0 is defined, which employs type erasure via a base class hierarchy with virtual dispatching to hide the concrete callable type.[17] Storage is handled through a customizable policy, often incorporating small-object optimization to allocate small functors (e.g., those fitting within a few pointers) on the stack, avoiding heap allocation overhead for common cases.[16] The operator() dispatches to the stored target by invoking a virtual operator() on an internal pointer, ensuring the correct callable is executed with the provided arguments while maintaining type safety at compile time.[17]
A representative example illustrates binding a member function:
cpp
class Widget {
public:
void draw() { /* ... */ }
};
Functor0<void> cmd(&Widget::draw, &myWidget);
cmd(); // Invokes myWidget.draw()
class Widget {
public:
void draw() { /* ... */ }
};
Functor0<void> cmd(&Widget::draw, &myWidget);
cmd(); // Invokes myWidget.draw()
Here, the constructor accepts a member function pointer and the target object, storing them in a derived class that implements the virtual interface.[16] For free functions or other functors, similar overloads capture the pointer directly.[16]
Key features include implicit conversion to bool, which returns true if the functor holds a valid callable (i.e., is not empty) and false otherwise, facilitating null checks in conditional expressions.[17] This is implemented via an operator void*() or direct operator bool() that inspects the internal storage pointer.[16] Additionally, the design supports copy semantics and assignment, propagating the stored callable appropriately.[17]
The advantages lie in providing a zero-overhead abstraction over raw function pointers, as the type erasure introduces no runtime cost beyond a virtual call (which can be devirtualized in many cases) and leverages small-object allocation for efficiency.[16] This results in a lightweight, flexible mechanism that promotes generic programming principles, enabling functors to be passed polymorphically in templates or containers without sacrificing performance.[16] In the Loki library accompanying the book, this is realized as Loki::Functor<R, TList>, configurable with threading models for concurrent use.[17]
Smart Pointers
In Modern C++ Design, Andrei Alexandrescu introduces a policy-based framework for smart pointers that extends beyond basic automatic memory management, allowing customization of ownership, storage, conversion, and checking behaviors through template parameters.[18] This approach builds on policy-based design principles to create highly flexible pointer classes suitable for diverse scenarios, such as multithreaded environments or specialized object lifetimes.[19]
The core class is defined as template <class T, class OwnershipPolicy, class StoragePolicy> class SmartPtr, where T is the pointee type, OwnershipPolicy governs how ownership is transferred or shared among instances, and StoragePolicy controls the internal representation and access to the pointee.[18] Ownership policies include RefCounted, which uses reference counting to manage shared ownership by incrementing and decrementing a counter upon copying and destruction, and Compressive, which ensures exclusive ownership by deeply copying the pointee object during smart pointer assignment to avoid shared state.[20] These ownership policies support behaviors such as deep copying for isolation or shallow copying via sharing for efficiency in read-only contexts.[18]
Reference counting in the RefCounted policy can be implemented as intrusive, where the pointee class includes a built-in counter member, or non-intrusive, relying on external allocation of the counter alongside the object to avoid modifying the pointee.[19] Thread-safe variants, such as RefCountedMT, incorporate mutex locks around counter operations to prevent race conditions in concurrent access, though at the cost of added overhead.[18] For example, a basic SmartPtr instantiation might appear as:
cpp
template <class T, template <class> class OP, template <class> class SP>
class SmartPtr {
// Implementation details using OP<T*> and SP<T>
public:
SmartPtr(T* p) : storage_(p), ownership_(p) {}
~SmartPtr() { ownership_.Release(storage_.GetImplementation()); }
// Operators * and -> delegated to StoragePolicy
};
template <class T, template <class> class OP, template <class> class SP>
class SmartPtr {
// Implementation details using OP<T*> and SP<T>
public:
SmartPtr(T* p) : storage_(p), ownership_(p) {}
~SmartPtr() { ownership_.Release(storage_.GetImplementation()); }
// Operators * and -> delegated to StoragePolicy
};
Conversion rules are handled via an optional ConversionPolicy, enabling safe downcasting through explicit or implicit rules; for instance, DisallowConversion prevents unsafe pointer type changes, while AllowConversion permits them under controlled conditions to support polymorphism without risking invalid casts.[20]
Compared to the Boost smart pointer library, which provides fixed implementations like shared_ptr for reference-counted ownership and intrusive_ptr for embedded counters, the Loki SmartPtr emphasizes policy flexibility, allowing users to mix and match behaviors (e.g., thread-safe reference counting with compressed storage) without creating separate classes.[19] This configurability influenced later C++ standard library developments, such as std::shared_ptr, but offers greater customization at the expense of complexity.[18]
Object Factories
In Modern C++ Design, object factories are presented as a technique for creating instances of polymorphic types in a type-safe manner without relying on runtime type information (RTTI). The clone factory implements the virtual constructor pattern, leveraging typelists to enumerate the creatable derived types from a common base class, enabling the creation of exact type copies at runtime while ensuring compile-time verification of type validity.
A key example is the OpNewFactory class template, defined as template <class TList> class OpNewFactory, where TList is a typelist of concrete types deriving from a base class. The factory's Create method takes an integer index corresponding to a position in the typelist and returns a pointer to the base class (base*), internally using operator new to instantiate the selected derived type. For instance, if the typelist contains types Widget and Gadget both inheriting from Base, calling Create(0) would allocate and return a Widget as a Base*, with the index-based selection enforced at compile time to prevent invalid accesses.
These factories provide significant benefits, including independence from RTTI for type identification and creation, which reduces overhead and potential errors in polymorphic systems. Additionally, the use of typelists ensures compile-time type safety, catching mismatches between indices and types early in the development process.
A variant of the clone factory employs prototypes, where each registered type maintains a prototype instance from which new objects are cloned via a virtual Clone method. This default factory approach allows for more flexible initialization, as the prototypes can encapsulate default state, while still adhering to the typelist-based selection mechanism for type safety.
The Loki Library
Library Structure
The Loki library originated as an open-source C++ template library developed by Andrei Alexandrescu to accompany his 2001 book Modern C++ Design: Generic Programming and Design Patterns Applied, with its initial release coinciding with the book's publication and the project registered on SourceForge on June 18, 2001.[21][4] The library implements the advanced template-based techniques described in the book, such as policy-based design, providing reusable components for design patterns and idioms without requiring binary distribution.[4]
Loki is structured as a primarily header-only library, consisting of modular header files organized under the loki namespace to encapsulate its components and avoid naming conflicts.[22] Key modules are grouped logically, with examples including Loki::Typelist for type list manipulations in Typelist.h and Loki::SmartPtr for customizable smart pointers in SmartPtr.h, allowing users to include only the necessary headers for their needs.[23] While most functionality is template-based and compiles inline, a few optional source files (e.g., SmallObj.cpp for small-object allocation) provide runtime support for specific features, though the core remains header-driven for ease of integration.[22]
The library depends on C++98 features, particularly partial template specialization, to enable its metaprogramming capabilities, making it compatible with compilers supporting the ISO C++ standard from 1998 onward, such as GCC 3.4 and MSVC 7.1.[22] Compiling the entire Loki library became an informal benchmark for assessing a compiler's template metaprogramming support during the early 2000s, as its intricate template dependencies tested conformance to C++98 requirements.[4]
Loki was actively maintained through community contributions on SourceForge until approximately 2010, with the last official release in February 2009 (version 0.1.7), after which development slowed as C++ evolved.[21] Its innovations, particularly in typelists and policy-based designs, significantly influenced subsequent libraries like Boost.MPL, which adopted and extended Loki's metaprogramming approaches for broader use in the C++ ecosystem.[23]
Key Components and Examples
The Loki library provides several key utilities that exemplify policy-based design and template metaprogramming techniques introduced in Modern C++ Design. Among these, TypelistOps offer a suite of compile-time operations for manipulating typelists, which are homogeneous sequences of types used to enable type-safe, generic programming. These operations include Append, which concatenates a type or typelist to an existing one; Erase, which removes a specified type; and others like Replace and IndexOf for type manipulation. For instance, a typelist can be defined and extended as follows:
cpp
#include "loki/Typelist.h"
typedef Loki::Typelist<int, Loki::Typelist<double, Loki::NullType> > MyList;
typedef Loki::Append<MyList, char>::Result NewList; // Results in Typelist<int, double, char>
typedef Loki::Erase<NewList, double>::Result FilteredList; // Removes double, yielding Typelist<int, char>
#include "loki/Typelist.h"
typedef Loki::Typelist<int, Loki::Typelist<double, Loki::NullType> > MyList;
typedef Loki::Append<MyList, char>::Result NewList; // Results in Typelist<int, double, char>
typedef Loki::Erase<NewList, double>::Result FilteredList; // Removes double, yielding Typelist<int, char>
This allows developers to build type catalogs at compile time for factories, visitors, and other patterns without runtime overhead.[4]
The SmartPtr class implements a customizable smart pointer using policy-based design, separating concerns such as ownership, checking, storage, and conversion into interchangeable policies. The OwnershipPolicy, in particular, dictates how reference counting or deletion occurs, such as through deep copying or reference counting. A common example uses the RefCounted policy for shared ownership:
cpp
#include "loki/SmartPtr.h"
struct Widget { /* ... */ };
typedef Loki::SmartPtr<Widget, Loki::RefCounted> SharedWidget;
SharedWidget p1(new Widget()); // Acquires [ownership](/page/Ownership)
SharedWidget p2 = p1; // Shares [ownership](/page/Ownership) via reference count
// p1 and p2 release when out of scope, deleting the Widget when count reaches zero
#include "loki/SmartPtr.h"
struct Widget { /* ... */ };
typedef Loki::SmartPtr<Widget, Loki::RefCounted> SharedWidget;
SharedWidget p1(new Widget()); // Acquires [ownership](/page/Ownership)
SharedWidget p2 = p1; // Shares [ownership](/page/Ownership) via reference count
// p1 and p2 release when out of scope, deleting the Widget when count reaches zero
This modular approach enables tailoring the smart pointer to specific needs, like COM interfaces or intrusive counting, while ensuring exception safety.[19]
Loki's Functor utility generalizes function objects to handle arbitrary argument counts and types via the GenScatterHierarchy technique, which scatters arguments to a hierarchy of base classes at compile time, avoiding virtual functions for efficiency. This enables multi-argument functors with type safety. An example demonstrates creating a functor that accepts two integer arguments:
cpp
#include "loki/Functor.h"
struct AddFunctor {
typedef int ResultType;
int operator()(int a, int b) { return a + b; }
};
typedef Loki::Functor<int, Loki::Typelist<int, Loki::Typelist<int, Loki::NullType> > > BinaryIntFunctor;
BinaryIntFunctor func(AddFunctor());
int result = func(3, 4); // Calls AddFunctor::operator()(3, 4), yielding 7
#include "loki/Functor.h"
struct AddFunctor {
typedef int ResultType;
int operator()(int a, int b) { return a + b; }
};
typedef Loki::Functor<int, Loki::Typelist<int, Loki::Typelist<int, Loki::NullType> > > BinaryIntFunctor;
BinaryIntFunctor func(AddFunctor());
int result = func(3, 4); // Calls AddFunctor::operator()(3, 4), yielding 7
GenScatterHierarchy derives the functor from bases that unpack tuple-like arguments, supporting up to a configurable maximum arity.[4]
Singletons in Loki use policies for creation, lifetime, and thread safety, allowing customization such as thread-safe instantiation via double-checked locking or Phoenix-style singletons destroyed before main exits. The CreateUsingNew policy with ThreadSafe creation ensures atomic initialization:
cpp
#include "loki/Singleton.h"
struct Logger {
Logger() { /* initialize */ }
void Log(const char* msg) { /* log implementation */ }
};
typedef Loki::SingletonHolder<Logger, Loki::CreateUsingNew, Loki::LongevityLifetime::DieAsChild> SafeLogger;
SafeLogger::Instance().Log("Thread-safe access"); // Initializes once, thread-safely
#include "loki/Singleton.h"
struct Logger {
Logger() { /* initialize */ }
void Log(const char* msg) { /* log implementation */ }
};
typedef Loki::SingletonHolder<Logger, Loki::CreateUsingNew, Loki::LongevityLifetime::DieAsChild> SafeLogger;
SafeLogger::Instance().Log("Thread-safe access"); // Initializes once, thread-safely
This policy combination provides destruction order control and avoids static initialization issues in multithreaded environments.[4]
A notable application is multi-method dispatch, where Loki::AssocVector—a sorted vector of key-value pairs offering map-like lookup with vector efficiency—facilitates dynamic typing for visitor patterns or multimethods. In an example from the book, AssocVector is used in a double dispatcher that stores pairs of type_info pointers as keys with function pointers for dispatching based on two argument types:
cpp
#include "loki/AssocVector.h"
#include <typeinfo>
typedef std::pair<const std::type_info*, const std::type_info*> KeyType;
typedef void (*CallbackType)(void*, void*);
typedef Loki::AssocVector<KeyType, CallbackType> DispatchTable;
DispatchTable table;
// Insertion via templated Add method (simplified):
// template <class SomeLhs, class SomeRhs>
// void Add(CallbackType fun) {
// KeyType key(&typeid(SomeLhs), &typeid(SomeRhs));
// table[key] = fun;
// }
// e.g., table.Add<int, double>(&HandleIntDouble);
// Dispatch:
KeyType key(&typeid(arg1), &typeid(arg2));
DispatchTable::Iterator it = table.Find(key);
if (it != table.End()) {
(it->second)(&arg1, &arg2);
}
#include "loki/AssocVector.h"
#include <typeinfo>
typedef std::pair<const std::type_info*, const std::type_info*> KeyType;
typedef void (*CallbackType)(void*, void*);
typedef Loki::AssocVector<KeyType, CallbackType> DispatchTable;
DispatchTable table;
// Insertion via templated Add method (simplified):
// template <class SomeLhs, class SomeRhs>
// void Add(CallbackType fun) {
// KeyType key(&typeid(SomeLhs), &typeid(SomeRhs));
// table[key] = fun;
// }
// e.g., table.Add<int, double>(&HandleIntDouble);
// Dispatch:
KeyType key(&typeid(arg1), &typeid(arg2));
DispatchTable::Iterator it = table.Find(key);
if (it != table.End()) {
(it->second)(&arg1, &arg2);
}
This enables runtime resolution for operations like geometric shape intersections, extending single dispatch to multiple arguments without excessive virtual hierarchies. Small-object allocator integration can optimize such structures for frequent small allocations.[24]
Influence and Legacy
Impact on C++ Development
The techniques introduced in Modern C++ Design significantly influenced the development of the Boost C++ Libraries, particularly through the Loki library's implementations that served as precursors to standardized components. The book's SmartPtr template, a policy-based smart pointer framework, is referenced in the Boost documentation as a resource for those interested in customizable smart pointers, paralleling some mechanisms in boost::shared_ptr such as ownership and deletion customization while providing thread-safe reference counting.[25] Similarly, the typelist utilities outlined in the book, enabling compile-time type manipulation, formed a foundational influence on the Boost Metaprogramming Library (MPL), which generalized these concepts into a comprehensive framework for template metaprogramming.[23]
These ideas extended beyond Boost into practical applications in high-performance domains. Metaprogramming patterns from the book, such as typelists and policy-based designs, have potential applications in game engines and embedded systems to optimize resource management and enable flexible, compile-time configurations without runtime overhead.
A core legacy of the book lies in popularizing policy-based design, a paradigm that decomposes classes into interchangeable policies for storage, threading, and behavior, influencing subsequent C++ standards. This approach contributed to the evolution of allocator models in the standard library, enabling customizable memory management.[26] The book's impact is evidenced by over 1,300 academic citations as of November 2025, reflecting its role in advancing generic programming.[27] It also inspired key follow-up works, such as C++ Template Metaprogramming by David Abrahams and Aleksey Gurtovoy, which expanded on typelist and metaprogramming tools from Boost MPL.[23]
Despite its contributions, the book's techniques have faced criticism for producing verbose, complex code due to the limitations of pre-C++11 templates, often requiring extensive boilerplate for type lists and policies. Many of these approaches have been partially superseded by C++11 features like variadic templates and auto, which streamline metaprogramming, and later by C++20 concepts, which provide compile-time constraints to make policy-based designs more readable and maintainable.[28] The Loki library, as a practical embodiment of these ideas, highlighted both their power and the need for language evolution to reduce syntactic overhead.[29]
Modern Relevance and Successors
Despite the evolution of C++ standards since its publication, Modern C++ Design remains a foundational text for understanding advanced template metaprogramming techniques, particularly in educational settings as of 2023, where it is included in university courses on advanced C++ topics.[30][31] It continues to be relevant for maintaining and extending legacy codebases written in pre-C++11 styles, where techniques like typelists and policy-based design provide efficient, compile-time solutions without runtime overhead.[32]
Many concepts from the book have found successors in the C++ standard library, such as the evolution of Boost.MPL's metaprogramming facilities into standard utilities like std::tuple and std::variant, which enable type-safe, generic data handling inspired by early template patterns. Policy-based designs, a core theme, are reflected in std::allocator_traits, which standardizes allocator behaviors through traits and customization points, allowing flexible memory management without altering core container logic.
In contemporary C++, analogs to the book's policy-like interfaces are formalized by C++20 concepts, which enforce compile-time requirements on templates, reducing errors and improving readability in generic code—much like the introspective designs advocated for resource-constrained environments.[33] Efforts to modernize the accompanying Loki library for C++20 and later standards highlight its ongoing influence, with community initiatives adapting its patterns for low-latency applications.[34]
Andrei Alexandrescu's later work on the D programming language incorporates enhanced generics that build on C++ template ideas, offering more expressive and safer parameterization for systems programming. However, the book has no direct integrations with C++23 features, which focus more on executors and pattern matching rather than metaprogramming overhauls.
Certain limitations have emerged with modern C++: the recursion-heavy template techniques, such as those in typelists, are less essential today due to constexpr functions and lambdas introduced in C++11, which simplify compile-time computations and generic algorithms.[35]