- •Вычисления значений
- •Пример:
- •Вычисления типов
- •Преимущества мета подхода:
- •Польза использования mpl:
- •Числовая метафункция
- •Свойства
- •Метаданные
- •Специализация
- •Первичный шаблон:
- •Инстанцирование:
- •Метафункции
- •Полиморфизм
- •Возможности свойств:
- •Организация свойств в Boost:
- •Пример:
- •Типовые ассоциации
- •Альтернатива:
- •Метафункции Продвижение (пересылка) метафункций
- •Класс метафункции
- •Формат:
- •Числовые
- •Пример:
- •Нульарные
- •Метрический анализ (Анализ размерностей)
- •Метрики
- •Величины
- •Сложение и вычитание
- •Умножение
- •Класс метафункции
- •Деление
- •Пример:
- •Метафункции высшего порядка (Каскады вложенных вызовов)
- •Использование apply:
- •Заполнители
- •Безымянный заполнитель:
- •Определение выражений с заполнителями
- •Лямбда (Лямбда метафункции/выражения)
- •Частичное приложение метафункции
- •Лямбда и Шаблоны Неметафункции
- •Обертки
- •Выбор типа
- •Ленивый Выбор Типа
- •Логические Операторы
- •Целочисленные обертки
- •Составные Операторы
- •Операторы с булевым значением:
- •Операторы для сравнения значений:
- •Операторы для интегральных значений:
- •Побитовые операторы:
- •Арифметические операторы:
- •Целочисленная «стенография»
- •Интегральные последовательности
- •Формат:
- •Последовательности
- •Концепции
- •Итераторы
- •Двунаправленные
- •Произвольные
- •Последовательности
- •Двунаправленные
- •Произвольные
- •Расширенные
- •Ассоциативные
- •Требования
- •Расширения
- •Расширенные ассоциативные последовательности
- •Алгоритмы и последовательности
- •Итераторы
- •Равенство
- •Операции Внутренние операции с последовательностей
- •Наследование
- •Создание последовательности
- •Реализация:
- •Создание итератора
- •Преимущества полной специализации перед частичной:
- •Добавление расширения
- •Алгоритмы
- •Абстракция:
- •Stl алгоритмы
- •Детали правильного использования stl:
- •Mpl алгоритмы
- •Различия stl и mpl алгоритмов:
- •Пример:
- •Вставки
- •Пример 1:
- •Пример 2:
- •Пример 3:
- •Фундамент алгоритмов
- •Пример:
- •Запрашиваемые алгоритмы
- •Последовательности (Алгоритмы, создающие последовательности)
- •Синонимы функциональных алгоритмов:
- •Создание алгоритма
- •Итераторы «Просмотр» и итераторы-адаптеры
- •Концепция просмотра
- •Применение просмотра Сравнения значений элементов последовательности:
- •Объединение множественных (мульти-) последовательностей
- •Упрощение 1
- •Упрощение 2 (создание просмотра)
- •Уход от ненужного вычисления
- •Выборочная обработка элемента
- •Итератор-адаптер
- •Создание просмотра
- •Реализация итератора-адаптера для zip_view.
- •Диагностика
- •Отладка ошибок
- •Трассировка инстанцирования
- •Исправленный пример:
- •Форматы сообщений об ошибках
- •Блок «with»
- •Устранение параметров шаблона по умолчанию
- •Глубокий typedef
- •Инструменты
- •Несколько компиляторов
- •(Статические Утверждения)
- •Формат:
- •Формат:
- •Индивидуальные сообщения утверждений
- •Настройка предиката:
- •(Генерирование встроенных сообщений)
- •Формат:
- •Выбор стратегии:
- •Тип печати
- •Кросс этапность компиляции/выполнения
- •Печать типов:
- •Тип посещения:
- •Выбор реализаций
- •Специализация шаблонов классов
- •Тег диспетчеризации
- •Выбор структуры
- •Композиция классов
- •Указатели на функцию
- •Ослабление типизации
- •Пример:
- •Обобщение:
- •Динамический полиморфизм
- •Автоматизация
- •Сохранение интерфейса
- •Рекуррентный шаблон
- •Формат:
- •Генерирование функций
- •Управление разрешением перегрузки
- •Явное управление множеством перегрузки
- •Документация mpl Последовательности
- •Концепции
- •(Прямая последовательность)
- •(Двунаправленная Последовательность)
- •(Последовательность произвольного доступа)
- •(Расширенная последовательность)
- •(Расширенная последовательность начала)
- •(Расширенная последовательность конца)
- •(Ассоциативная последовательность)
- •(Расширенная ассоциативная последовательность)
- •(Обертка интегральной последовательности )
- •(Переменная последовательность)
- •Представления
- •Метафункции (Внутренние)
- •Итераторы
- •Концепции
- •(Прямой итератор)
- •(Двунаправленный итератор)
- •(Итератор произвольного доступа)
- •Метафункции
- •Алгоритмы
- •Концепции
- •(Инсертер: механизм вставок)
- •Инсертеры (инсертер: механизм вставок)
- •Итеративные алгоритмы
- •Запрашиваемые алгоритмы
- •Преобразования
- •Метафункции
- •Концепции Метафункция
- •Метафункциональный класс
- •Лямбда выражения
- •Заполнители
- •Тэг диспетчеризированные метафункции
- •Числовые метафункции
- •Тривиальные метафункции
Выбор структуры
Использование метафункций для настройки кложенных элементов классов/структур выглядит так:
template <class T>
struct X
{
static int m1 = metafunction1<T>::type::value;
typedef typename metafunction2<T>::type m2;
int m4(typename metafunction3<T>::type p);
//...
};
Так, вычисляется значение для m1, тип для m2 и тип параметра функции для m4.
Предположим, необходимо контролировать наличие м2 в той или иной специализации X. Подход, используемый выше позволяет нам управлять детали данного элементов класса, но фундаментальные структурные изменения требуют более мощной техники.
Выбор структуры содержит вставку модифицируемой части классовой структуры в простой открытый базовый класс или шаблоный базовоый класс и использование метапрограммы для выбора между ними. Чтобы увидеть, как это работает, давайте исправим проблемы в compose_fg:
template <class R, class F, class G>
class compose_fg
{
public:
compose_fg(F const& f, G const& g)
: f(f), g(g)
{}
template <class T>
R operator()(T const& x) const
{
return f(g(x));
}
private:
F f;
G g;
};
Первым решением было генерирование объекта следующим образом:
compose_fg<float,std::negate<float>,float(*)(float)>
так что F является std::negate<float>. В большинстве реализаций std:: negate содержит лишь операторную функцию осуществляющая инверсию своего аргумента:
T operator()(const T& x) const { return -x; }
Т.е., это пустой класс, т.к. в нем нет элементов данных. Хотя, стандарт C++ говорит, что каждый из элементов данных compose_fg должен занимать по крайней мере один байт. В типичной схеме класса компоновки, первый байт будет отведен для F f, хотя специализации negate не содержат элементов данных, ему необходимо несколько быйт (скажем, три) для выравнивания памяти под указатель на функции, и память для g (скажем, четыре байта), которая будет следовать после, т.о. объект занимает 8 байт памяти. Если не объявлять элементы данных класса, то вес compose_fg будет 1 байт и это предел, т.к. даже пустой класс должен иметь ненулевой размер.
Как таковых ограничения на расположение в памяти элементов данных класса нет, за исключением того, что они не могут занимать 1 адрес и перекрывать друг друга. Исключение: POD-класс, элементы данных располагаются в памяти более предсказуемо.
Одним из способов ликвидации хранения пустых базовых классов является их обнаружение (с помощью свойства boost::is_empty) и пропуск.
С этим подходом связано несколько проблем:
Отсутствие прозрачности: даже пустые классы могут иметь нетривиальные конструкторы и деструкторы, и если не хранить копии f и g, то различия в поведении compose_fg могут быть существенными.
Для реализации оператора() необходимы объекты F и G, если их не хранить, то в случае необходимости их нужно создавать, но они могут не содержать конструктора по умолчанию.
Лучшее решение: компиляторы могут осуществлять оптимизацию пустого базового класса (EBO – Empty Base Optimisation), которая позволяет разместить пустой базовый класс по адресу любого под-объекта данного класса, но, до тех пор пока этот адрес принадлежит лишь одному под-объекту данного типа.
Например:
compose_fg<float,std::negate<float>,float(*)(float)>
Функтор-композитор compose_fg может иметь идеальный размер, если будет организован так:
template <class R, class F, class G>
class compose_fg : F // if empty, F may overlap with g
{
public:
typedef R result_type;
compose_fg(F const& f, G const& g)
: F(f), g(g) // initialize base with f
{}
template <class T>
R operator()(T const& x) const
{
F const& f = *this; // retrieve F subobject
return f(g(x));
}
private:
G g;
};
Естественно, не получиться использовать эту структуру для всех специализаций compose_fg: если F будет указателем на функцию, то будет ошибка компиляции, поскольку указатели на функции не могут быть базовыми классами. Кроме того, эта структура не будет использоваться во всех случаях: когда G является пустым классом, а не F, то необходимо наследовать именно его. Потому необходим механизм выбора подходящей структуры в конкретный момент:
// base class template to be defined later
template <class F, bool F_empty, class G, bool G_empty> class storage;
template <class R, class F, class G>
class compose_fg
: storage<
F, boost::is_empty<F>::value,
G, boost::is_empty<G>::value
>
{
private:
typedef
storage<
F,boost::is_empty<F>::value,
G,boost::is_empty<G>::value
> base;
public:
compose_fg(F const& f, G const& g)
: base(f, g)
{}
template <class T>
R operator()(T const& x) const
{
F const& f = this->get_f();
G const& g = this->get_g();
return f(g(x));
}
};
Теперь написать класс storage так, чтобы он имел правильную структуру для каждой из четырех комбинаций F_empty и G_empty, и предоставлял доступ к сохраненному F и G посредством get_f и get_g:
//+--------------------------------------------------------------------+
// neither F nor G is empty
template <class F, class G>
class storage<F, false, G, false>
{
protected:
storage(F const& f, G const& g)
: f(f), g(g)
{}
F const& get_f() { return f; }
G const& get_g() { return g; }
private:
F f;
G g;
};
//+--------------------------------------------------------------------+
// G is empty
template <class F, class G>
class storage<F,false,G,true>
: private G
{
protected:
storage(F const& f, G const& g)
: G(g), f(f)
{}
F const& get_f() { return f; }
G const& get_g() { return *this; }
private:
F f;
};
//+--------------------------------------------------------------------+
// F is empty
template <class F, class G>
class storage<F,true,G,false>
: private F
{
protected:
storage(F const& f, G const& g)
: F(f), g(g)
{}
F const& get_f() { return *this; }
G const& get_g() { return g; }
private:
G g;
};
//+--------------------------------------------------------------------+
// F and G are both empty
template <class F, class G>
class storage<F,true,G,true>
: private F, private G
{
protected:
storage(F const& f, G const& g)
: F(f), G(g)
{}
F const& get_f() { return *this; }
G const& get_g() { return *this; }
};
Нет никаких гарантий, что конкретный компилятор будет выполнять автоматическую оптимизацию, но для тех, которые ее выполняют, данная структура обеспечит более эффективное использование элементов данных класса.
В библиотеке Boost есть шаблон compressed_pair, который обобщает механизм оптимизации EBO.
