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

Управление разрешением перегрузки

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

Например, если мы напишем общобщенную функцию drive, которая оперирует транспортными средствами (где автомобиль это концепция), то можно написать:

template <class Vehicle>

void drive(Vehicle const& v)

{ ... }

Проблема возникает, когда будет написана одноименная функция drive, но с другим типом параметра (например, Screw):

template <class Screw>

void drive(Screw const& s)

{ ... }

Отличие имени шаблонного параметра не является личием типов для компилятора. Если такие фукнции находятся в одном пространстве имен – ошибка компиляции, если в разных - неоднозначность или наршуение правила одного определения, а в случае поиска функции посредством ADL возможен вообще вызов другой функции.

В результате ADL сформирует множество перегруженных функций из которых необходимо будет сделать выбор или выдать ошибку.

#include <list>

namespace utility

{

// fill the range with zeroes

template <class Iterator>

Iterator clear(Iterator const& start, Iterator const& finish);

// perform some transformation on the sequence

template <class Iterator>

int munge(Iterator start, Iterator finish)

{

// ...

start = clear(start, finish);

// ...

}

}

namespace paint

{

template <class Canvas, class Color> // generalized template

void clear(Canvas&, Color const&);

struct some_canvas { };

struct black { };

std::list<some_canvas> canvases(10);

int x = utility::munge(canvases.begin(), canvases.end()); // вызов

}

В данном случае будет ошибка компиляции, т.к. итераторы list являеются шаблонным классом параметризованным как paint::some_canvas. ADL выполняет поиск функции clear в пространстве имен paint, которая добавлена к множеству перегруженных функций. В теле munge вызов print::clear выглядит предпочтительнее, чем utility::clear для передачи аргументов. Т.к. в данном случае тип возаращаемого значения – void, то будет выдана ошибка компиляции, но если бы функция print::clear возвращала Canvas&, то ошибка может быть незамечена, что приведет к неопределенным последствиям.

Решение: применим паттерн CRTP для идентификации модели концепции Vehicle и Screw. Необходимо добавить требование, что бы модели каждой концепции открыто наследовали базовый класс CRTP:

template <class Derived>

struct vehicle

{};

template <class Derived>

struct screw

{};

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

template <class Vehicle>

void drive(vehicle<Vehicle> const& v)

{

Vehicle const& v_= static_cast<Vehicle const&>(v);

//...

};

template <class Screw>

void drive(screw<Screw> const& s)

{

Screw const& s_= static_cast<Screw const&>(s);

//...

};

Явное управление множеством перегрузки

Иногда, CRTP недостаточно для ограничения достижения обобщенных аргументов шаблонной функции. Например, необходимо, чтобы шаблонная функция оперировала встроенными типами (которые не могут иметь базовые классы), или сторонними типами, которые не должны изменяться. Если мы можем определить типы аргументов во время компиляции, то набор enable_if из Boost, позволяют управлять множеством перегрзки.

Например, следующая шаблонная функция применяется только к итераторам арифметических типов. Применим boost::iterator_value, метафункцию, которая извлекает value_type итератора.

#include <iterator>

#include <boost/utility/enable_if.hpp>

#include <boost/type_traits/is_arithmetic.hpp>

#include <boost/iterator/iterator_traits.hpp>

template <class Iterator>

typename boost::enable_if<

boost::is_arithmetic< // enabling condition

typename boost::iterator_value<Iterator>::type

>,

typename boost::iterator_value<Iterator>::type // return type

>::type

sum(Iterator start, Iterator end)

{

typename boost::iterator_value<Iterator>::type x(0);

for(; start != end; ++start) x += *start;

return x;

}

Если «::value» условие С - правда, то enable_if <C,T>::type будет T, поэтому функция sum просто возвращает объект value_type итератора. В противном случае, sum просто исчезает из процесса разрешения перегрузки!

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

Если просто написать:

std::iterator_traits<Iterator>::value_type

вместо enable_if <...>::type, то вызывая sum с итераторами, чей value_type явялется std::vector<int> - приведет к ошибке в работе sum, при поптыке использовать оператор «+=». Если value_type итераторов являлся бы бы std::string, то компиляция скорее всего выполнится, но в будущем возможно неопределенное поведение работы функции.

Так, можно самостоятельно организовать допустимое множество для перегрузки. Например, добавить к уже имеющемуся, возможность вызова функции с типом итератора vector<vector<int>> и других вложенных контейнеров арифметических типов.

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

// given an Iterator that points to a container, get the

// value_type of that container's iterators.

template <class Iterator>

struct inner_value

: boost::iterator_value<

typename boost::iterator_value<Iterator>::type::iterator

>

{};

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

template <class Iterator>

typename boost::lazy_disable_if<

boost::is_arithmetic< // disabling condition

typename boost::iterator_value<Iterator>::type

>,

inner_value<Iterator> // result metafunction

>::type

sum(Iterator start, Iterator end)

{

typename inner_value<Iterator>::type x(0);

for (; start != end; ++start)

x += sum(start->begin(), start->end());

return x;

}

Слово «disable» в lazy_disable_if означает, что функция будет удалена из множества перегрузки устанавливается, если условие дает правду. Слово «lazy» означает, что элемент «::type» явялется результатом вызова второго аргумента, как нульарной метафункции.

Для полноты: enable_if.hpp содержит шаблоны disable_if и lazy_enable_if, версии с суффиксом «_C» всех четырех шаблонов, принимают целочисленные константы вместо обертки, в качестве первого аргумента.

Обратите внимание, что inner_value <Iterator> может быть вызван только, если значение типа Iterator является еще одним итератором. В противном случае, будет получена ошибка.

Определение enable_if:

template <bool, class T = void>

struct enable_if_c

{

typedef T type;

};

template <class T>

struct enable_if_c<false, T>

{};

template <class Cond, class T = void>

struct enable_if

: enable_if_c<Cond::value, T>

{};

Когда условие является ложным, enable_if_c<C,T>::type не существует! Стандарт C++ касательно правил разрешения перегрузки говорят, что, когда вывод аргумента функции шаблона не удается, он не вносит ничего в набор кандидатов рассматриваемых функций для данного вызова и такой вызов не является ошибкой. Этот принцип называется «неверная подстановка не является ошибкой» «Substitution Failure Is Not An Error» (SFINAE), Дэвид Вандердвуд и Николай Джосьютис [VJ02].

Причина использования inner_value и ленивых вычислений: несмотря на то, что enable_if не приведит к ошибке. Правила вывода аргументов шаблона (14.8.2, пункт 2), перечисляют условия, при которых неудачный вывод аргументов шаблона является ошибкой. Оказывается, что форма enable_if находится в этом списке, но ошибка возможна только во время инчтанцирования шаблонной функции (например, iterator_value), а не во время вывода, потому ленивые вычисления помогают использовать этот факт.

sizeof

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

Эта техника лежит в основе многочисленных низкоуровневых шаблонных метафункций, в том числе и в Boost TypeTraits Library.

Например можно написать свойство, отделяющая классы и объединения от всех других типов:

typedef char yes; // sizeof(yes) == 1

typedef char (&no)[2]; // sizeof(no) == 2

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

template <class T>

struct is_class_or_union

{

// SFINAE eliminates this when the type of arg is invalid

template <class U>

static yes tester(int U::*arg);

// overload resolution prefers anything at all over "..."

template <class U>

static no tester(...);

// see which overload is chosen when U == T

static bool const value

= sizeof(tester<T>(0)) == sizeof(yes);

typedef mpl::bool_<value> type;

};

struct X{};

BOOST_STATIC_ASSERT(is_class_or_union<X>::value);

BOOST_STATIC_ASSERT(!is_class_or_union<int>::value);

Очень ограниченным есть тот факт, что получить можно только размер типа, а не сам тип по время выполнения. Например, было бы хорошо иметь возможность писать так:

// generalized addition function object

struct add

{

template <class T, class U>

typeof(T+U) operator()(T const& t, U const& u)

{

return t+u;

}

};

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