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

Тип посещения:

Для более общего решение проблемы сглаживания типов для кросс-функции, можно применить паттерн Visitor:

struct visit_type // generalized visitation function object

{

template <class Visitor>

void operator()(Visitor) const

{

Visitor::visit();

}

};

template <class T> // specific visitor for type printing

struct print_visitor

{

static void visit()

{

std::cout << typeid(T).name() << std::endl;

}

};

int main()

{

mpl::for_each<s, print_visitor<_1>>(visit_type());

}

Здесь функтор visit_type ожидает, что ее аргумент будет иметь статическую функцию-класса visit. Так, можно строить новые объекты-посетители для любых целей.

Выбор реализаций

Рассмотрим способы выбора поведения или интерфейсов на этапе выполнения, основываясь на некторых вычислениях этапа компиляции.

if

Самый простой способ контролировать реализацию шаблонной функции времени выполнения – проверка статических состояний в заголове инструкции if:

template <class T>

void f(T x)

{

if (boost::is_class<T>::value)

{

//...implementation 1...

}

else

{

//...implementation 2...

}

}

Так как условие может быть полностью определено во время компиляции, то многие компиляторы будут оптимизировать проверку is_class, генерируя код только для выбранной ветки (! Корректно только в том случае, если шаблонные данные обоих ветвей согласованы для типа любого передаваемого аргумента).

Данный метод не является универсальным.

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

Например, рассмотрим, что происходит, когда функции выше, применяется следующим образом:

template <class T>

void f(T x)

{

if (boost::is_class<T>::value)

{

std::cout << x::value; // handle integral wrappers

}

else

{

std::cout << x; // handle non-wrappers

}

}

Цель была иметь возможность выводить значение интегрального типа (например, int) или интегральной константной оболочку (например, long_ <5>). Если вызвать f(42), то будет ошибка компиляции. Причина: в теле функции f будут проверены сразу обе ветви инструкции if и передача интегрального типа int приведет к ошибке, т.к. для нее не существует элемента «::value».

Специализация шаблонов классов

Разделим ветви инструкии if на различные статичесные функции-класса нескольких специализаций шаблона. По специализации можно будет решить, какое поведение выбрать:

template <bool> // handle integral constant wrappers

struct f_impl

{

template <class T>

static void print(T x) { std::cout << x::value; }

};

template <> // specialization for non-wrappers

struct f_impl<false>

{

template <class T>

static void print(T x) { std::cout << x; }

};

template <class T>

void f(T x)

{

f_impl<boost::is_class<T>::value>::print(x);

};

Тег диспетчеризации

Теги диспетчеризации времени выполнения использует перегрузку функции для генерирования исполняемого кода на основе свойств типа.

Хорошим примером явялется реализация алгоритма advance стандартной библиотеки. Концептуально данный алгоритм перемещает итератор i на n позиций, в зависимости от вида итератора, выбирается та или иная реализация данного алгоритма, поддерживающая особую логику реализации обхода последовательности итератором конкретного вида. Например, если i поддерживает произвольный доступ, то алгоритм смещения может быть реализована как «i+=n» и является очень эффективным: постоянная сложность независимо от велечины смещения. Для двунаправленного итератора необходимо делать выбор мжду реализацией инкрементации/декрементации, попытка вызвать операцию декрементации для прямого итератора должна приводить к ошибке компиляции.

Таким образом, алгоритм advance требует, по крайней мере три различных реализаций. Выбор одной из них будет зависеть от концепции того или иного типа категории тега:

Иерархия одиночного наследования в виде спсика:

namespace std

{

struct input_iterator_tag { };

struct forward_iterator_tag

: input_iterator_tag { };

struct bidirectional_iterator_tag

: forward_iterator_tag { };

struct random_access_iterator_tag

: bidirectional_iterator_tag { };

}

Тег является просто пустым классом, единственной целью которого - передача некоторой информации во время компиляции, в данном случае моделирование концепции итератора согласно полученному типу итератора. Каждый итератор типа I имеет ассоциированный с ним тег категории, которые могут быть доступны как

std::iterator_traits<I>::iterator_category

В данном случае теги принадлежат иерархии наследования, которая уточняет иерархию концепций, которую они представляют. Например, каждый двунаправленный итератор также явялется прямым итератором, также и тэг двунаправленного итератора наследует тэг прямого итератора.

Выбор реализаций времени выполнения будет осуществляться посредством перегрузки функции и использования тэгов в качестве аргументов.

namespace std

{

//+--------------------------------------------------------------------+

// Реализация итератора ввода.

template <class InputIterator, class Distance>

void __advance_impl

(

InputIterator& i,

Distance n, i

input_iterator_tag

)

{

while (n--) ++i;

}

//+--------------------------------------------------------------------+

// Реализация двунаправленного итератора.

template <class BidirectionalIterator, class Distance>

void __advance_impl

(

BidirectionalIterator& i,

Distance n,

bidirectional_iterator_tag

)

{

if (n >= 0)

while (n--) ++i;

else

while (n++) --i;

}

//+--------------------------------------------------------------------+

// Реализация итератора произвольного доступа.

template <class RandomAccessIterator, class Distance>

void __advance_impl

(

RandomAccessIterator& i,

Distance n,

random_access_iterator_tag

)

{

i += n;

}

//+--------------------------------------------------------------------+

// Алгоритм является диспетчеризатором по тегу.

template <class InputIterator, class Distance>

void advance(InputIterator& i, Distance n)

{

typedef typename

iterator_traits<InputIterator>::iterator_category category;

__advance_impl(i, n, category());

}

}

Польза иерархий наследования:

Выбирается та перегруженная версия __advance_impl, тип параметов которой наиболее соответствует типу тега. Польза иерархии наследования итераторов для работы тегов в том, что нет необходимости создавать реализации __advance_impl, для итераторов производных классов (forward_iterator_tag – производный от input_iterator_tag), так, например, компилятор автоматически выбирает версию input_iterator_tag для всех входных и прямых итераторов. Это было бы невозможным при использовании специализации типов тегов для выбра реализации.

Нюанс: mpl::true_ и mpl::false_ являются тегами диспетчеризации. В примере, приведенном ниже, desperate_cast <T> (х) эквивалентна static_cast <T> (х), если х не является (указатель на) объектом полиморфного класса, в противном случае desperate_cast <T> (х) эквивалентна dynamic_cast <T> (х).

//+--------------------------------------------------------------------+

//| Реализация оболочек для операторов приведения. |

//| Если х не является (указатель на) объектом полиморфного класса, то |

//| desperate_cast<T>(х) == static_cast<T>(х), иначе |

//| desperate_cast<T>(х) == dynamic_cast<T>(х) |

//+--------------------------------------------------------------------+

// implementation for polymorphic types

template <class T, class U>

T desperate_cast_impl2(U& x, mpl::true_)

{

return dynamic_cast<T>(x); // legal iff U is polymorphic

}

//+--------------------------------------------------------------------+

// implementation for non-polymorphic types

template <class T, class U>

T desperate_cast_impl2(U& x, mpl::false_)

{

return static_cast<T>(x);

}

//+--------------------------------------------------------------------+

// dispatcher

template <class T, class U>

T desperate_cast_impl(U& x)

{

return desperate_cast_impl2<T>

(

x,

boost::is_polymorphic<

typename boost::remove_pointer<U>::type

>()

);

}

//+--------------------------------------------------------------------+

// public interface

template <class T, class U>

T desperate_cast(U const& x) { return desperate_cast_impl<T>(x); }

template <class T, class U>

T desperate_cast(U& x) { return desperate_cast_impl<T>(x); }

С помощью механизмов интегральных свойств типа, метафункция is_polymorphic <...>() определит по типу объекта, какой тег необходимо выбрать.

Генератор объекта - обобщенная функция, которая используется для вывода информации о типах, которая иначе может быть выведена только более трудным способом.

Рассмотрим следующий шаблон, содержащий композицию двух вызываемых объектов, f и g. Это функтор-композитор, обеспечивает вложенные вызовы f(g(х)) с аргументом х и возвращает значение типа R:

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;

};

Следующий пример использует композитора, примененяя sin2(x) для каждого элемента последовательности:

#include <functional>

#include <algorithm>

#include <cmath>

float input[5] = {0.0, 0.1, 0.2, 0.3, 0.4};

float output[5];

float sin_squared(double x) { return std::sin(std::sin(x)); }

float* ignored =

std::transform(

input, input+5, output,

compose_fg<float, std::negate<float>, float(*)(float)> // явные типы параметров

(

std::negate<float>(), &sin_squared

)

);

Чтобы упростить запись, для автоматического вывода шаблонных параметров класса необходимо использовать специальную функцию посредника – генератор объекта, т.к. функции, в отличии от классов, поддерживают автоматический вывод шаблонных типов:

template <class R, class F, class G>

compose_fg<R,F,G> compose(F const& f, G const& g)

{

return compose_fg<R,F,G>(f,g);

}

Теперь алгоритм std::transform может быть вызван следующим образом:

float* ignored = std::transform(

input, input+5, seq2,

compose<float>(std::negate<float>(), &sin_squared)

);

Так, в списке параметра шаблона необходимо указать только <float>, для определения типа возвращаемого значения.

Стандратные шаблонные функции bind1st и bind2nd аналогичные генераторы объектов, с механизмом связывания.

Для связывания аргументов с параметрами шаблонов по умолчанию, можно использовать библиотеку Boost Bind library, которая предоставляется больше возможностей.

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