Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
template_metaprogramming_ru.docx
Скачиваний:
1
Добавлен:
01.07.2025
Размер:
774.38 Кб
Скачать

Числовые

Числовые Метафункции - являются интегральными обертками, чей вложенный «::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, который является и быстрым и корректным.

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]