- •Вычисления значений
- •Пример:
- •Вычисления типов
- •Преимущества мета подхода:
- •Польза использования mpl:
- •Специализация
- •Первичный шаблон:
- •Инстанцирование:
- •Метафункции
- •Числовая метафункция
- •Свойства
- •Метаданные
- •Полиморфизм
- •Возможности свойств:
- •Организация свойств в Boost:
- •Пример:
- •Типовые ассоциации
- •Альтернатива:
- •Метафункции Продвижение (пересылка) метафункций
- •Класс метафункции
- •Формат:
- •Числовые
- •Пример:
- •Нульарные
- •Метрический анализ (Анализ размерностей)
- •Метрики
- •Величины
- •Сложение и вычитание
- •Умножение
- •Класс метафункции
- •Деление
- •Пример:
- •Метафункции высшего порядка (Каскады вложенных вызовов)
- •Использование apply:
- •Заполнители
- •Безымянный заполнитель:
- •Определение выражений с заполнителями
- •Лямбда (Лямбда метафункции/выражения)
- •Частичное приложение метафункции
- •Лямбда и Шаблоны Неметафункции
- •Обертки
- •Выбор типа
- •Ленивый Выбор Типа
- •Логические Операторы
- •Целочисленные обертки
- •Составные Операторы
- •Операторы с булевым значением:
- •Операторы для сравнения значений:
- •Операторы для интегральных значений:
- •Побитовые операторы:
- •Арифметические операторы:
- •Целочисленная «стенография»
- •Интегральные последовательности
- •Формат:
- •Последовательности
- •Концепции
- •Итераторы
- •Двунаправленные
- •Произвольные
- •Последовательности
- •Двунаправленные
- •Произвольные
- •Расширенные
- •Ассоциативные
- •Требования
- •Расширения
- •Расширенные ассоциативные последовательности
- •Алгоритмы и последовательности
- •Итераторы
- •Равенство
- •Операции Внутренние операции с последовательностей
- •Наследование
- •Создание последовательности
- •Реализация:
- •Создание итератора
- •Преимущества полной специализации перед частичной:
- •Добавление расширения
- •Алгоритмы
- •Абстракция:
- •Stl алгоритмы
- •Детали правильного использования stl:
- •Mpl алгоритмы
- •Различия stl и mpl алгоритмов:
- •Пример:
- •Вставки
- •Пример 1:
- •Пример 2:
- •Пример 3:
- •Фундамент алгоритмов
- •Пример:
- •Запрашиваемые алгоритмы
- •Последовательности (Алгоритмы, создающие последовательности)
- •Синонимы функциональных алгоритмов:
- •Создание алгоритма
- •Итераторы «Просмотр» и итераторы-адаптеры
- •Концепция просмотра
- •Применение просмотра Сравнения значений элементов последовательности:
- •Упрощение 1
- •Упрощение 2 (создание просмотра)
- •Объединение множественных (мульти-) последовательностей
- •Уход от ненужного вычисления
- •Выборочная обработка элемента
- •Итератор-адаптер
- •Создание просмотра
- •Реализация итератора-адаптера для zip_view.
- •Диагностика
- •Отладка ошибок
- •Трассировка инстанцирования
- •Исправленный пример:
- •Форматы сообщений об ошибках
- •Блок «with»
- •Устранение параметров шаблона по умолчанию
- •Глубокий typedef
- •Инструменты
- •Несколько компиляторов
- •(Статические Утверждения)
- •Формат:
- •Формат:
- •Индивидуальные сообщения утверждений
- •Настройка предиката:
- •(Генерирование встроенных сообщений)
- •Формат:
- •Выбор стратегии:
- •Тип печати
- •Кросс этапность компиляции/выполнения
- •Печать типов:
- •Тип посещения:
- •Выбор реализаций
- •Специализация шаблонов классов
- •Тег диспетчеризации
- •Выбор структуры
- •Композиция классов
- •Указатели на функцию
- •Ослабление типизации
- •Пример:
- •Обобщение:
- •Динамический полиморфизм
- •Автоматизация
- •Сохранение интерфейса
- •Рекуррентный шаблон
- •Формат:
- •Генерирование функций
- •Управление разрешением перегрузки
- •Явное управление множеством перегрузки
- •Документация mpl Последовательности
- •Концепции
- •(Прямая последовательность)
- •(Двунаправленная Последовательность)
- •(Последовательность произвольного доступа)
- •(Расширенная последовательность)
- •(Расширенная последовательность начала)
- •(Расширенная последовательность конца)
- •(Ассоциативная последовательность)
- •(Расширенная ассоциативная последовательность)
- •(Обертка интегральной последовательности )
- •(Переменная последовательность)
- •Представления
- •Метафункции (Внутренние)
- •Итераторы
- •Концепции
- •(Прямой итератор)
- •(Двунаправленный итератор)
- •(Итератор произвольного доступа)
- •Метафункции
- •Алгоритмы
- •Концепции
- •(Инсертер: механизм вставок)
- •Инсертеры (инсертер: механизм вставок)
- •Итеративные алгоритмы
- •Запрашиваемые алгоритмы
- •Преобразования
- •Метафункции
- •Концепции Метафункция
- •Метафункциональный класс
- •Лямбда выражения
- •Заполнители
- •Тэг диспетчеризированные метафункции
- •Числовые метафункции
- •Тривиальные метафункции
Числовые
Числовые Метафункции - являются интегральными обертками, чей вложенный «::value» - интегральная константа.
struct five // integral constant wrapper for the value 5
{
static int const value = 5;
typedef int value_type;
//...more declarations...
};
Для доступа к значению числового метафункционального результата необходимо записать:
metafunction-name<type arguments...>::type::value
Аналогично, интегральные константы передаются метафункциям в подобных обертках (эквивалентно связи константа-переменная на динамическом уровне, что позволяет транслировать значения констант по программе, посредством переменных - оберток).
Этот дополнительный уровень абстракции требует, чтобы все метафункции принимали и возвращали типы, что делает их более универсальными, более полиморфными, и более взаимодействующими.
Цель: такие метафункции, которые принимают другие метафункции в качестве аргументов. Так как метафункции - шаблоны, а не типы, мы не можем передать их сущностям, принимающим типы, не выполнив инчтанцирование шаблона для конкретного типа.
Соглашения об оформлении «::value» (метафункциональное продвижение):
Постоянная запись «::type::value» для вычисления интегральной константы – утомительна. Решение: можно обеспечить вложенное «::value» непосредственно в метафункции. Все числовые метафункции MPL организованы так. Поэтому, обращаясь в внешнему «::value», можно посрдством него обратится в вложенному «::value», даже если метафункция приводит к числовому результату.
Пример:
Выбор поведения на этапе компиляции.
Усовершенствуем реализацию iter_swap:
Если попытаться передать iter_swap итератор для std::list<std::vector<std:: string>>, которые выполняют итерации по векторам строк, то профилировщик, сообщит, что iter_swap - узкое местом производительности,
template <class ForwardIterator1, class ForwardIterator2>
void iter_swap(ForwardIterator1 i1, ForwardIterator2 i2)
{
typename
iterator_traits<ForwardIterator1>::value_type tmp = *i1; //1
*i1 = *i2;
*i2 = tmp;
}
первый оператор в iter_swap делает копию значения, на которое ссылается i1. Начинается копирование вектора - копирование всех его элементов, и каждый строковый элемент, скопированный или присвоенный, вероятно, потребует динамического выделения памяти и поразрядной копии символов строки.
Решение: стандартная библиотека обеспечивает эффективную версию обмена содержимого векторов, которая только обменивается несколькими внутренними указателями, так, можем сказать нашему клиенту просто разыменовывать итераторы и вызвать swap для результатов:
std::swap(*i1, *i2);
Это не совсем то, что требуется. Можно было бы попробовать добавить дополнительный уровень абстракции – оболчку для обмена конкретными типами.
template <class ForwardIterator1, class ForwardIterator2>
void iter_swap(ForwardIterator1 i1, ForwardIterator2 i2)
{
std::swap(*i1,*i2);
}
Но, если типы данных, на которые ссылаются итераторы будут отличными, то
Обмен будет невозможным (swap работает с одинаковыми типами).
template <class T> void swap(T& x, T& y);
Можно было бы попытаться решить эту проблему, оставляя прежнюю медленную реализацию iter_swap, но при добавлении перегрузки:
// Generalized (slow) version
template <class ForwardIterator1, class ForwardIterator2>
void iter_swap(ForwardIterator1 i1, ForwardIterator2 i2)
{
typename
iterator_traits<ForwardIterator1>::value_type
tmp = *i1;
*i1 = *i2;
*i2 = tmp;
}
// Добавляем перегрузку для одинаковых типов итераторов:
template <class ForwardIterator>
void iter_swap(ForwardIterator i1, ForwardIterator i2)
{
std::swap(*i1, *i2); // sometimes faster
}
Правила C++ для частичного упорядочивания шаблонных функций гласят, что новая перегрузка - лучше соответствует, если ее типы параметров лучше соответсвуют аргументам. Это решает проблему для разных типов «int*» и «long*», что приведет к вызову первой версии, а для одинаковых типов – второй.
Рассмотрим что происходит, когда мы вызываем iter_swap для итераторов vector<string> и list<string>. У двух итераторов будет тот же самый value_type – дает возможность использовать эффективный swap но, так как сами итераторы - различные типы, то правила разрешения перегрузки функций приведут к вызову более медленной версии (для разных типов).
Цель: заставить iter_swap работать над двумя различными типами итераторов, которые совместно используют одинаковый value_type.
Можем пытаться переписать swap, таким образом, чтобы она работала для различных типов:
template <class T1, class T2>
void swap(T1& a, T2& b)
{
T1 tmp = a;
a = b;
b = tmp;
}
Это может помочь в большинстве случаев, но не во всех. Есть категория итераторов, для которых этот сопосб не подходит: «operator*», который возвращает прокси-ссылку.
Прокси-ссылка - не просто ссылка, а класс, который пытается эмулировать ее. Для итератора это и читаемо и перезаписываемо, ссылка прокси - только класс, который и конвертируем к, и присваиваем от value_type. Самый известный пример такого итератора - пример вектора <bool> - контейнер, который хранит в каждом своем элементе бит. Нет способа получить реальную ссылку на бит, но прокси эмулирует свое поведение так, что вектор ведет себя почти также как и любой другой вектор. Прокси «operator= (bool)» записывает в вектор соответствующий бит, а его оператор bool () возвращает true, если и только если этот бит в векторе является установленным.
Предметом того, как vector<bool> и его iterators вписываются в стандарт, был предметом больших дебатов.
struct proxy
{
proxy& operator=(bool x)
{
if(x)
bytes[pos/8] |= (1u << (pos%8)); // | Побитовое сложение (установка бита)
else
bytes[pos/8] &= ~(1u << (pos%8)); // & Побитовое умножение (сбивает бит)
return *this;
}
operator bool() const
{
// Ничего не меняет, а вернет реальное значение бита:
return bytes[pos/8] & (1u << (pos%8)); // (1u << 0) == 1; (1u << 7) == 8
}
unsigned char* bytes;
size_t pos; // 0-7
};
struct bit_iterator
{
typedef bool value_type;
typedef proxy reference;
//more typedefs...
proxy operator*() const;
//more operations...
};
Когда iter_swap разыменовывает bit_iterator и пытается выдать несколько ссылок прокси на std::swap, происходит измененеие переданных ей аргументов (итераторов), т.к. они передаются по неконстантной ссылке. Проблема в том, что прокси, возвращенные «operator*», являются временными объектами, и компилятор выдаст ошибку, когда мы попытаемся передать временные объекты как ссылочные неконстантые аргументы. Большую часть времени это - правильное решение. Исходная реализация iter_swap, тем не менее, хорошо работает для итерторов для vector<bool>.
Цель: применять быструю реализацию iter_swap только тогда, когда у итертаоров одинаковый value_type, и их ссылочные типы - реальные ссылки, а не прокси. Для решения этой проблемы необходимо определять явялется ли тип T реальной ссылкой и действительно ли оба value_types имеют одинаковый тип.
Boost содержит множество фундаментальных свойств, которые помогают идентифицировать данные , например, на предмет «ссылочности». Учитывая соответствующие свойства типа, можно решить, вызывать swap или отдельную специализацию.
#include <boost/type_traits/is_reference.hpp>
#include <boost/type_traits/is_same.hpp>
#include <iterator> // for iterator_traits
#include <utility> // for swap
template <bool use_swap> struct iter_swap_impl;
namespace std
{
template <class ForwardIterator1, class ForwardIterator2>
void iter_swap(ForwardIterator1 i1, ForwardIterator2 i2)
{
// Полная диагностика первого итератора:
typedef iterator_traits<ForwardIterator1> traits1;
typedef typename traits1::value_type v1;
typedef typename traits1::reference r1;
// Полная диагностика второго итератора:
typedef iterator_traits<ForwardIterator2> traits2;
typedef typename traits2::value_type v2;
typedef typename traits2::reference r2;
// Выбор версии обмена:
bool const use_swap =
boost::is_same<v1,v2>::value &&
boost::is_reference<r1>::value &&
boost::is_reference<r2>::value;
//---Диспетчеризация поведения---
}
}
В метке «---Диспетчеризация поведения---» мы должны выбрать поведение, основанное на значении use_swap. Можно обеспечить эти два поведения в специализациях iter_swap_impl (вне тела iter_swap):
// Обрати внимание на вложенность шаблонных функций, что
// позволяет по параметру класса-оболчки вызывать ту
// или иную вложенную функцию, без необходимости дописывать
// дополнительный параметр bool в список параметров каждой
// функции-класса:
template <>
struct iter_swap_impl<true> // the "fast" one
{
template <class ForwardIterator1, class ForwardIterator2>
static void do_it(ForwardIterator1 i1, ForwardIterator2 i2)
{
std::swap(*i1, *i2);
}
};
template <>
struct iter_swap_impl<false> // the one that always works
{
template <class ForwardIterator1, class ForwardIterator2>
static void do_it(ForwardIterator1 i1, ForwardIterator2 i2)
{
typename
iterator_traits<ForwardIterator1>::value_type
tmp = *i1;
*i1 = *i2;
*i2 = tmp;
}
};
Теперь iter_swap_impl <use_swap>::do_it обеспечивает соответствующую реализацию iter_swap для любого возможного значения use_swap. Поскольку do_it - статическая функция класса, iter_swap может вызвать его, не создавая объект iter_swap_impl:
//---Диспетчеризация поведения---
iter_swap_impl<use_swap>::do_it(*i1,*i2);
С помощью механизма дополнительных абстракций и частичной специализации удалось реализовать алгоритм iter_swap, который является и быстрым и корректным.
