Алхимический C++: где происходит волшебство

В огромном мире программирования немногие языки обладают такой универсальностью и мощью, какие предлагает C++. Как язык, известный своей эффективностью, производительностью и широким спектром приложений, C++ стал одним из основных продуктов для разработчиков, стремящихся создавать надежные и масштабируемые программные решения. В то время как новички часто погружаются в основы программирования на C++, существует множество продвинутых методов и фрагментов кода, которые могут поднять их навыки до новых высот.

В этой статье я хочу приступить к исследованию глубин C++, углубляясь в фрагменты кода продвинутого уровня, которые раздвигают границы того, что может быть достигнуто с помощью этого замечательного языка. Независимо от того, являетесь ли вы опытным программистом на C++, стремящимся расширить свои знания, или амбициозным учеником, стремящимся освоить тонкости языка, я тщательно отобрал, основываясь на своем опыте, коллекцию фрагментов кода, которые, я надеюсь, бросят вам вызов и вдохновят вас.

Наше путешествие выведет нас за пределы базового синтаксиса и общих конструкций программирования, раскрывая тайны C++ и раскрывая его истинный потенциал. Приготовьтесь к захватывающему опыту, пока мы разгадываем сложности C++ и раскрываем секреты, скрытые под его поверхностью.

Почему С++?

C++ — это язык, который занимает особое место в мире программирования по нескольким причинам. Вот несколько ключевых причин, по которым разработчики часто выбирают C++ для программирования продвинутого уровня:

  1. Производительность и эффективность. C++ известен своей высокой производительностью и эффективностью, что делает его идеальным выбором для приложений, требующих быстрого выполнения и оптимального использования ресурсов. Благодаря возможности прямого доступа к памяти и аппаратным ресурсам C++ позволяет разработчикам точно настраивать свой код для достижения максимальной производительности.
  2. Низкоуровневый контроль: C++ обеспечивает низкоуровневый контроль над аппаратными ресурсами, позволяя разработчикам управлять памятью, манипулировать указателями и напрямую управлять оборудованием. Этот уровень контроля особенно ценен в сценариях, где требуется детальный контроль над системными ресурсами, такими как встроенные системы, разработка игр и высокопроизводительные вычисления.
  3. Объектно-ориентированное программирование (ООП). C++ — это объектно-ориентированный язык программирования, что означает, что он поддерживает создание объектов и управление ими, инкапсуляцию данных и функций, наследование и полиморфизм. ООП позволяет разработчикам писать модульный, повторно используемый и поддерживаемый код, что упрощает управление крупномасштабными проектами.
  4. Стандартная библиотека шаблонов (STL). C++ поставляется с богатой стандартной библиотекой шаблонов (STL), которая предоставляет набор мощных структур данных (таких как векторы, списки и карты) и алгоритмов. STL позволяет разработчикам использовать предварительно созданные компоненты, экономя время и силы при выполнении общих операций с данными и алгоритмических задач.
  5. Совместимость и переносимость. C++ хорошо совместим с другими языками программирования и системами. Его можно легко интегрировать с существующими кодовыми базами и библиотеками, написанными на C, что позволяет разработчикам использовать обширную экосистему библиотек C. Кроме того, компиляторы C++ доступны для различных платформ, что делает его легко переносимым между различными операционными системами.
  6. Внедрение в отрасли. C++ имеет долгую историю внедрения в отрасли и широко используется в различных областях, включая разработку игр, системное программирование, финансы, научные вычисления и многое другое. Его надежность, производительность и универсальность сделали его идеальным языком для создания сложных и критически важных приложений.
  7. Почему бы и нет? После многолетнего опыта работы в этой области я по-прежнему оставался верным C++, который не переставал меня удивлять. Так почему бы не?

Эти причины делают C++ популярным выбором для программирования продвинутого уровня, поскольку он предлагает разработчикам мощный и гибкий набор инструментов для решения сложных задач программирования. Однако важно отметить, что выбор языка программирования в конечном итоге зависит от конкретных требований и контекста проекта.

Алхимия

Итак, если вы готовы отправиться в путешествие в мир продвинутого программирования на C++, давайте начнем нашу экспедицию и раскроем всю мощь этого замечательного языка!

Вычисление последовательности Фибоначчи во время компиляции с использованием метапрограммирования шаблонов

#include <iostream>

template <int N>
struct Fibonacci {
    static constexpr int value =
        Fibonacci<N - 1>::value + Fibonacci<N - 2>::value;
};

template <>
struct Fibonacci<0> {
    static constexpr int value = 0;
};

template <>
struct Fibonacci<1> {
    static constexpr int value = 1;
};

int main() {
    std::cout << Fibonacci<10>::value;
    return 0;
}

Этот код реализует вычисление последовательности Фибоначчи во время компиляции с использованием метапрограммирования шаблонов на C++. Давайте разберем это шаг за шагом.

Код определяет структуру шаблона с именем Fibonacci, которая вычисляет последовательность Фибоначчи во время компиляции. Он принимает целочисленный параметр N в качестве аргумента шаблона. Затем внутри структуры Fibonacci имеется статическая переменная-член value, в которой хранится вычисленное значение Фибоначчи для заданного аргумента шаблона N. Значение определяется рекурсивным добавлением двух предыдущих значений Фибоначчи: Fibonacci<N - 1>::value и Fibonacci<N - 2>::value.

Затем код специализируется на структуре Fibonacci для двух базовых случаев: когда N равно 0 и когда N равно 1. В обоих случаях члену value присваиваются соответствующие значения Фибоначчи 0 и 1.

Наконец, функция main выводит значение 10-го числа Фибоначчи, вызывая Fibonacci<10>::value и печатая его с помощью std::cout.

Самый неприятный разбор

Непростая задача синтаксического анализа возникает в языке программирования C++, когда дело доходит до разрешения контринтуитивной формы синтаксической неоднозначности. В определенных сценариях грамматика C++ не различает определение параметра объекта и указание типа функции. В таких случаях компилятор обязан интерпретировать строку как спецификацию типа функции.

#include <iostream>

class MyClass {
public:
    MyClass(int value) {
        std::cout << "An integer value: " << value;
    }
};

int main() {
    MyClass obj(MyClass(8));
    
    return 0;
}

Код определяет класс с именем MyClass. Внутри класса есть единственный общедоступный конструктор, который принимает целочисленный параметр value. Цель конструктора — вывести на консоль сообщение, указывающее, что он был вызван, и отображающее предоставленное значение.

Внутри функции main объявлена ​​и инициализирована с аргументом MyClass(8) переменная типа MyClass с именем obj. При инициализации obj вызывается конструктор MyClass. Он выводит на консоль сообщение 'Целочисленное значение: 8'.

Опущение типа иautoиспользование ключевых слов

#include <iostream>
#include <typeinfo>

using namespace std;

int main() {
    auto x = 42;
    auto& y = x;
    auto&& z = 3.14;

    cout << typeid(x).name() << endl;
    cout << typeid(y).name() << endl;
    cout << typeid(z).name() << endl;

    return 0;
}

В этом коде есть два дополнительных понятия: ссылки (auto&) и ссылки rvalue (auto&&).

Строка auto& y = x; объявляет y как ссылку на x, а тип y выводится как int&, указывая, что это ссылка на целое число. В то время как строка auto&& z = 3.14; объявляет z как ссылку rvalue на инициализатор 3.14, который является литералом с плавающей запятой. Тип z выводится как double&&, представляющий ссылку rvalue на double.

Мы используем оператор typeid и функцию-член name() из класса std::type_info для вывода имен типов x, y и z.

Во время выполнения программа выводит:

Параметры шаблона-шаблона и вариативные шаблоны

#include <iostream>
#include <list>
#include <vector>

using namespace std;

template <template <typename...> class Container>
void printSize(const Container<int>& container) {
    cout << "Size: " << container.size() << endl;
}

int main() {
    vector<int> vec = {1, 2, 3, 4, 5};
    printSize(vec);

    list<int> lst = {1, 2, 3};
    printSize(lst);
    
    return 0;
}

Предоставленный код демонстрирует использование параметров шаблона-шаблона в C++ для создания функции, которая печатает размер контейнера. Во-первых, мы определили шаблон функции printSize(), который принимает контейнер в качестве аргумента. Параметр Container template-template указывает, что он принимает шаблон с одним или несколькими параметрами типа. Функция предназначена для контейнеров, содержащих int элементов.

Внутри функции мы используем аргумент container и вызываем функцию-член size() для получения размера контейнера. Затем мы печатаем размер, используя cout.

В функции main мы создаем два контейнера: vector<int> vec и list<int> lst. Мы передаем эти контейнеры в качестве аргументов функции printSize(). Поскольку оба контейнера специализированы для int, функция может быть вызвана для каждого из них.

Любопытно повторяющийся шаблон шаблона (CRTP) и статический полиморфизм

Любопытно повторяющийся шаблон шаблона (CRTP) — это продвинутый метод программирования на C++, который включает использование наследования и шаблонов для достижения статического полиморфизма. CRTP позволяет производному классу наследоваться от базового класса, обеспечивая при этом собственную реализацию определенного поведения, определенного в базовом классе.

В CRTP класс шаблона служит базовым классом, а производный класс создается путем наследования от базового класса шаблона, при этом производный класс служит аргументом шаблона. Это создает форму полиморфизма времени компиляции, когда поведение производного класса определяется аргументом шаблона.

#include <iostream>

using namespace std;

template <typename Derived>
struct Base {
    void foo() {
        static_cast<Derived*>(this)->fooImpl();
    }
};

struct Derived : Base<Derived> {
    void fooImpl() {
        cout << "Derived::fooImpl()";
    }
};

int main() {
    Derived obj;
    obj.foo();

    return 0;
}

Код определяет структуру шаблона с именем Base. Эта структура принимает параметр шаблона Derived, который, как ожидается, будет производным классом, унаследованным от Base<Derived>. Структура Base предоставляет функцию-член с именем foo(). Кроме того, внутри функции foo() есть static_cast, которая приводит указатель this к указателю типа Derived*. Это приведение позволяет производному классу получить доступ к собственной конкретной реализации функции fooImpl().

Затем код определяет структуру с именем Derived, производную от Base<Derived>. Он переопределяет функцию fooImpl() собственной реализацией.

Теперь внутри функции main() функция foo() вызывается для объекта obj. Поскольку Derived наследуется от Base<Derived>, вызывается функция foo() из базового класса. Однако из-за использования CRTP вызов foo() внутри Base разрешается в Derived::fooImpl().

Кроме того, Любопытно повторяющийся шаблон шаблона (CRTP) можно использовать для реализации Абстрактного синтаксического дерева (AST) на C++. CRTP может предоставить механизм для определения общего поведения и структуры в базовом классе, в то же время позволяя производным классам предоставлять конкретные реализации для различных типов узлов в AST.

Важность CRTP обусловлена ​​следующими преимуществами, которые он предлагает:

  1. Статический полиморфизм: CRTP допускает статический полиморфизм, что означает, что поведение производного класса определяется во время компиляции, а не во время выполнения. Это приводит к эффективному выполнению кода, поскольку исключаются накладные расходы на диспетчеризацию виртуальных функций и полиморфизм во время выполнения.
  2. Повторное использование и расширение кода. CRTP позволяет повторно использовать код, предоставляя механизм добавления функциональности к базовому классу без его изменения. Производный класс может специализировать или переопределять определенное поведение базового класса, определяя свои собственные функции или переменные. Это способствует модульному и поддерживаемому коду за счет разделения задач.
  3. Оптимизация производительности. Используя диспетчеризацию времени компиляции, CRTP может привести к оптимизации производительности. Поскольку вызовы функций разрешаются во время компиляции, избегаются накладные расходы, связанные с диспетчеризацией виртуальных функций, что приводит к более быстрому выполнению.
  4. Проверка типов и ошибки времени компиляции: CRTP позволяет выполнять проверку типов во время компиляции, гарантируя, что производный класс соответствует интерфейсу, определенному базовым классом. Любые нарушения или ошибки обнаруживаются во время компиляции, предотвращая ошибки во время выполнения.
  5. Реализация статических интерфейсов: CRTP можно использовать для реализации статических интерфейсов, где производный класс предоставляет набор статических функций или переменных, доступ к которым можно получить без создания экземпляра. Это может быть полезно для служебных классов или для обеспечения определенного поведения в группе классов.

Лямбда-выражения с захватом

#include <iostream>

using namespace std;

int main() {
    int x = 42;
    auto lambda = [x]() mutable {
        cout << "Captured x: " << x << endl;
        x++;
    };
    
    lambda();
    lambda();
    
    cout << "Updated x: " << x << endl;
    return 0;
}

Приведенный выше код демонстрирует использование лямбда-функции для захвата и изменения переменной. Лямбда захватывает переменную x по значению и может изменять ее копию внутри лямбда-функции. Однако изменения, внесенные в захваченную переменную, не влияют на исходную переменную вне лямбда-функции.

Функция main является точкой входа в программу. Внутри него объявляется целочисленная переменная x и инициализируется значением 42. Лямбда-функция определяется и присваивается переменной lambda. Лямбда-функция захватывает переменную x по значению ([x]) и помечает ее как изменяемую (mutable). Это позволяет лямбда-функции изменять захваченную переменную.

Внутри лямбда-функции есть оператор вывода, который печатает сообщение «Захвачено x:», за которым следует значение x с использованием cout. Затем значение x увеличивается (x++). Лямбда-функция вызывается вызовом lambda().

В первом выходном операторе выводится "Захвачено x: 42", потому что лямбда-функция захватывает x по значению во время своего создания. Лямбда-функция вызывается снова вызовом lambda(). В то время как второй выходной оператор печатает «Захвачено x: 43», потому что лямбда-функция изменяет захваченную переменную x, увеличивая ее.

Вне лямбда-функции есть еще один выходной оператор, который печатает сообщение "Updated x:", за которым следует значение x с использованием cout. Он выводит исходное значение x (42), поскольку модификации лямбда-функции не влияют на исходную переменную x, поскольку она была захвачена по значению.

Сбой замены не является ошибкой (SFINAE)

Ошибка замены не является ошибкой (SFINAE) — это принцип метапрограммирования шаблонов C++. Это относится к поведению компилятора при попытке подставить аргументы шаблона в процессе специализации шаблона.

В C++ шаблоны являются мощным механизмом, позволяющим выполнять универсальное программирование. При использовании шаблонов компилятор пытается сопоставить предоставленные аргументы шаблона с параметрами шаблона, чтобы определить подходящую специализацию для использования. Однако если в ходе этого процесса подстановка аргументов шаблона не удалась, компилятор не считает это ошибкой. Вместо этого он пробует другие доступные специализации или генерирует ошибку компиляции только в том случае, если подходящая специализация не найдена.

Концепция SFINAE часто используется в сочетании с перегрузкой функций шаблона и типажами. Это позволяет коду шаблона выборочно включать или отключать определенные специализации шаблона на основе свойств или возможностей предоставленных аргументов шаблона. Используя SFINAE, разработчики могут создавать более гибкий и универсальный код, который адаптируется к различным типам или условиям.

Принцип SFINAE может быть довольно сложным, включая сложные правила и методы, такие как использование std::enable_if, decltype, std::void_t и т. д. Это фундаментальный аспект метапрограммирования шаблонов C++, который широко используется в библиотеках и средах для достижения полиморфизма времени компиляции и диспетчеризации на основе типов.

#include <iostream>
#include <type_traits>

using namespace std;

template <typename T>
typename enable_if<is_integral<T>::value, T>::type
multiplyByTwo(T value) {
    return value * 2;
}

int main() {
    // Will compile
    std::cout << multiplyByTwo(5) << std::endl;

    // Error
    //std::cout << multiplyByTwo(3.14) << std::endl;
    
    return 0;
}

Код определяет шаблон функции с именем multiplyByTwo, который принимает один параметр шаблона T. Он возвращает T и использует черты типа enable_if и is_integral для условного включения функции только для целочисленных типов.

Внутри шаблона функции enable_if используется для включения функции, если is_integral<T>::value оценивается как true. Ключевое слово typename используется для указания зависимого типа. Признак типа is_integral проверяет, является ли тип T интегральным типом (например, int, long и т. д.). Он предоставляет статическую переменную-член value, которая имеет значение true, если T является интегральным типом, и false в противном случае.

Если условие enable_if выполнено (т. е. T является интегральным типом), функция умножает параметр value на 2 и возвращает результат.

Внутри функции main() функция multiplyByTwo вызывается с аргументом типа 5, который является целочисленным литералом. Первый оператор cout выводит результат вызова multiplyByTwo(5), то есть 10, на консоль. А второй оператор cout намеренно закомментирован, чтобы продемонстрировать, что вызов multiplyByTwo() с нецелочисленным аргументом (например, 3.14) приведет к ошибке компиляции.

Важность SFINAE заключается в его способности обеспечивать более гибкий и универсальный код за счет специализации шаблонов и перегрузки. Позволяя компилятору автоматически выбирать наилучшее совпадение из набора перегруженных шаблонных функций, SFINAE позволяет создавать код, который может обрабатывать различные типы или комбинации аргументов общим способом.

  1. Улучшенная гибкость кода. SFINAE позволяет писать общий код, который может обрабатывать различные типы и условия. Он позволяет создавать экземпляры шаблонных функций с различными типами аргументов и выполнять различные действия в зависимости от доступных замен.
  2. Проверка типов во время компиляции: SFINAE предоставляет механизм для выполнения проверки типов во время компиляции. Он позволяет выборочно включать или отключать функции шаблона в зависимости от наличия или отсутствия определенных функций-членов или типов в аргументах.
  3. Устойчивость к ошибкам: SFINAE предотвращает прямые ошибки при создании экземпляров шаблонов. Вместо того, чтобы вызывать сбой компиляции, он позволяет компилятору учитывать другие специализации шаблона или перегрузки функций, что приводит к изящной деградации или альтернативному поведению.
  4. Выражение SFINAE. С появлением C++11 выражение SFINAE расширяет концепцию SFINAE, допуская сбой подстановки на основе допустимости определенного выражения, включающего аргументы шаблона. Это обеспечивает более тонкий контроль над специализацией шаблонов и разрешением перегрузок.

Попытательные блоки функционального уровня для обработки исключений

На самом деле, я обнаружил этот синтаксис случайно. Я понятия не имею, как часто используется этот синтаксис, но я полагаю, что немногие знают об этом, поскольку я не видел на GitHub кода с обработчиком try-catch на функциональном уровне.

#include <iostream>

using namespace std;

int divide(int x, int y)
    try {
        if(y == 0)
            throw runtime_error("Divide by zero");

        return x / y;
    }
    catch(const exception& e) {
        cout << "Error: " << e.what() << endl;
        return 0;
    }

int main() {
    // Output: 5
    cout << divide(10, 2) << endl;

    // Output: Error
    cout << divide(10, 0) << endl;

    return 0;
}

Код определяет функцию с именем divide, которая принимает два целочисленных параметра, x и y. Он использует блок try для инкапсуляции кода, который может вызвать исключение. Находясь внутри блока try, код проверяет, равен ли y нулю. Если это так, генерируется исключение типа runtime_error с сообщением "Делить на ноль".

Если y не равно нулю, код возвращает результат деления x на y (x / y). Если внутри блока try возникает исключение, поток управления передается соответствующему блоку catch.

Блок catch перехватывает исключения типа const exception& (которые могут включать исключения, производные от std::exception). Внутри блока catch доступ к перехваченному исключению осуществляется через параметр e. Затем код выводит сообщение «Ошибка:», за которым следует сообщение об исключении, полученное путем вызова e.what(). После обработки исключения в блоке catch код возвращает 0 в качестве результата.

Теперь функция divide вызывается дважды с разными аргументами внутри функции main(). Первый оператор cout выводит результат вызова divide(10, 2), равный 5, на консоль. Второй оператор cout выводит на консоль результат вызова divide(10, 0), который генерирует исключение. Исключение перехватывается в блоке catch, и отображается соответствующее сообщение об ошибке.

Шаблон Variadic для проверки того, является ли тип специализацией шаблона

По сути, приведенный ниже код демонстрирует способ проверки того, является ли данный тип специализацией определенного класса шаблона в C++.

#include <iostream>
#include <map>
#include <type_traits>
#include <vector>

using namespace std;

template <typename T, template <typename...> class Template>
struct is_specialization_of: false_type {};

template <template <typename...> class Template, typename... Args>
struct is_specialization_of<Template<Args...>, Template>: true_type {};

template <typename T>
void print_specialization() {
    if constexpr (is_specialization_of<T, vector>::value)
        cout << "Type is a specialization of std::vector" << endl;
    else if constexpr (is_specialization_of<T, map>::value)
        cout << "Type is a specialization of std::map" << endl;
    else cout << "Type is not a specialization of std::vector or std::map" << endl;
}

int main() {
    // Output: Type is a specialization of std::vector
    print_specialization<std::vector<int>>();

    // Output: Type is a specialization of std::map
    print_specialization<std::map<int, std::string>>();

    // Output: Type is not a specialization of std::vector or std::map
    print_specialization<std::string>();
    
    return 0;
}

Как видно выше, код определяет признак типа с именем is_specialization_of как структуру. Он используется для проверки того, является ли тип T специализацией шаблонного класса Template. Основной шаблон is_specialization_of определяется с помощью тега специализации, установленного на false_type.

Более того, частичная специализация is_specialization_of определена для случая, когда T соответствует шаблону Template<Args...>. В этом случае для тега специализации установлено значение true_type.

Между тем, шаблон функции print_specialization принимает один параметр шаблона T и используется для печати, является ли T специализацией vector, map или ни тем, ни другим.

Внутри print_specialization оператор if constexpr используется для выполнения условного ветвления во время компиляции. Если T является специализацией std::vector, код выводит сообщение "Тип является специализацией std::vector". Если T является специализацией std::map, код выводит сообщение "Тип является специализацией std::map." В противном случае, если ни одно из условий не выполняется, код печатает сообщение "Тип не является специализацией std::vector или std::map." эм>

Функция print_specialization вызывается три раза с разными типами в качестве аргументов шаблона. Первый вызов print_specialization с std::vector<int> в качестве аргумента шаблона выводит сообщение "Тип является специализацией std::vector". Второй вызов print_specialization с std::map<int, string> в качестве аргумента шаблона выводит сообщение «Тип — это специализация std::map». И, наконец, третий вызов print_specialization с string в качестве аргумента шаблона выводит сообщение «Тип не является специализацией std::vector или std:: карту.”

Вот, у нас есть! Алхимический синтаксис для C++, который может творить чудеса!

Что теперь?

Подводя итог этой исчерпывающей статье о фрагментах кода C++ продвинутого уровня, мы рассмотрели ряд тем, расширяющих границы программирования на C++. Начиная с вычислений во время компиляции с использованием метапрограммирования шаблонов и заканчивая сложными функциями языка, такими как Самый раздражающий разбор и пропуском типов с ключевым словом auto, мы углубились в глубины C++, чтобы раскрыть его истинную мощь. Кроме того, мы рассмотрели передовые методы, такие как параметры шаблона-шаблона, вариативные шаблоны, Любопытно повторяющийся шаблон шаблона (CRTP) и статический полиморфизм. Мы также изучили лямбда-выражения с захватом, принцип Ошибка подстановки не является ошибкой (SFINAE) и пробные блоки функционального уровня для обработки исключений. Кроме того, мы исследовали, как можно использовать вариативные шаблоны для проверки того, является ли тип специализацией шаблона.

Следовательно, C++ — это язык, который продолжает удивлять программистов своей глубиной и универсальностью. Изучив фрагменты кода продвинутого уровня, мы убедились в истинной мощи C++ и его способности решать сложные задачи программирования. Темы, затронутые в этой статье, от вычислений во время компиляции до сложных языковых конструкций и волшебства шаблонов, позволили заглянуть в огромный потенциал, который таится в C++. По мере изучения этих передовых концепций стало очевидно, что освоение C++ требует непрерывного обучения и экспериментов. Включив эти методы в наш арсенал программирования, мы получаем возможность создавать более эффективные, надежные и элегантные решения.

Также важно помнить, что это лишь верхушка айсберга. Язык C++ обширен и постоянно развивается, предлагая бесконечные возможности для инноваций и творчества. Погружаясь в тонкости C++, мы не только расширяем свои знания в области программирования, но и открываем новые возможности для решения сложных задач. Лично я призываю вас продолжать свой путь исследований и открытий, экспериментируя с этими передовыми методами и расширяя границы того, чего можно достичь с помощью C++.

Мир C++ ждет, готовый вознаградить тех, кто отважится погрузиться в его глубины!

Для дополнительной литературы и ссылок:

  1. Абрахамс Д. и Гуртовой А. (2005). Метапрограммирование шаблонов C++: концепции, инструменты и методы от Boost и не только. Аддисон-Уэсли Профессионал.
  2. Александреску, А. (2001). Современный дизайн C++: применение общих шаблонов программирования и проектирования. Аддисон-Уэсли Профессионал. (стр. 175–200).
  3. Боккара, Дж. (2020). Понимание самого сложного синтаксического анализа в C++. Свободный С++. [Сообщение блога]. Получено с https://www.fluentcpp.com/2018/10/05/understanding-the-most-vexing-parse/
  4. Шиллинг, К. (2015). Демистификация списка C++ Lambda Capture. Множественность. Получено с https://www.pluralsight.com/guides/demystifying-the-c-lambda-capture-list
  5. Стоенко, А. (2016). Введение в любопытно повторяющийся шаблон шаблона (CRTP) в C++. Свободный С++. Получено с https://www.fluentcpp.com/2017/05/12/curiously-recurring-template-pattern/
  6. Страуструп, Б. (2013). Язык программирования C++ (4-е издание). Аддисон-Уэсли Профессионал. (стр. 47–59, 307–330 и 512–520).
  7. Саттер, Х. (2005). Исключительный C++: 47 инженерных головоломок, проблем программирования и решений. Аддисон-Уэсли Профессионал. (стр. 47–53).
  8. Вандевурде, Д., и Джосуттис, Н.М. (2017). Шаблоны C++: полное руководство (2-е издание). Аддисон-Уэсли Профессионал. (стр. 372–395, 474–497 и 549–572).