Метапрограммирование на C++ — различия между версиями
(→Substitution Failure Is Not An Error (SFINAE)) |
|||
(не показано 35 промежуточных версий 2 участников) | |||
Строка 1: | Строка 1: | ||
− | Мы рассмотрим несколько | + | Мы рассмотрим несколько <<необычных>> примеров использования шаблонов C++. |
− | == Статический assert == | + | == Статический <code>assert</code> == |
В следующем примере приведен код, который компилируется только | В следующем примере приведен код, который компилируется только | ||
на 64-разрядной платформе: | на 64-разрядной платформе: | ||
<source lang="cpp"> | <source lang="cpp"> | ||
− | char[sizeof(int*) | + | char a[sizeof(int*) == 8 ? 1 : -1]; |
− | <source> | + | </source> |
Если код компилируется не на 64-разрядной платформе, то | Если код компилируется не на 64-разрядной платформе, то | ||
− | sizeof(int*) != 8, что приведет к объявлению массива a отрицательного | + | <code>sizeof(int*) != 8</code>, что приведет к объявлению массива a отрицательного |
размера, а это запрещено стандартом. | размера, а это запрещено стандартом. | ||
− | Эта идея используется в макросе BOOST_STATIC_ASSERT, предоставляемом | + | Эта идея используется в макросе <code>BOOST_STATIC_ASSERT</code>, предоставляемом |
− | модулем Static Assert библиотеки Boost. | + | модулем [http://www.boost.org/doc/libs/1_46_1/doc/html/boost_staticassert.html <code>Static Assert</code>] |
+ | библиотеки Boost. | ||
== Tag passing == | == Tag passing == | ||
Строка 26: | Строка 27: | ||
</source> | </source> | ||
− | где p, q --- итераторы, указывающие на начало и конец массива, а элемент, | + | где <code>p</code>, <code>q</code> --- итераторы, указывающие на начало и конец массива, |
− | на который указывает m, после завершения работы функции будет располагаться | + | а элемент, на который указывает <code>m</code>, после завершения работы функции будет располагаться |
− | на месте элемента, на который указывает p. | + | на месте элемента, на который указывает <code>p</code>. |
Допустим, у нас есть разные реализации этой функции для разных типов итераторов: | Допустим, у нас есть разные реализации этой функции для разных типов итераторов: | ||
Строка 43: | Строка 44: | ||
Мы можем добавить к сигнатуре этих функций формальный параметр и перенести | Мы можем добавить к сигнатуре этих функций формальный параметр и перенести | ||
− | информацию о типе итератора, с которым работает эта функция из ее имени в этот | + | информацию о типе итератора, с которым работает эта функция, из ее имени в этот |
параметр: | параметр: | ||
Строка 67: | Строка 68: | ||
== Замена числовых идентификаторов на типы == | == Замена числовых идентификаторов на типы == | ||
− | С помощью следующего трюка можно переписать | + | С помощью следующего трюка можно переписать функцию, поведение которой |
− | зависит от числового идентификатора, | + | зависит от числового идентификатора, так, чтобы ее поведение зависело от |
формального параметра (как в предыдущем разделе): | формального параметра (как в предыдущем разделе): | ||
Строка 78: | Строка 79: | ||
<source lang="cpp"> | <source lang="cpp"> | ||
template<int i> | template<int i> | ||
− | struct int2type { | + | struct int2type { static const int value = i; }; |
− | + | ||
− | } | + | |
</source> | </source> | ||
Строка 89: | Строка 88: | ||
</source> | </source> | ||
− | == Substitution Failure Is Not An Error ( | + | == Substitution Failure Is Not An Error (SFINAE) == |
+ | При создании экземпляров шаблонных функций могут возникать ошибки компиляции. | ||
+ | Рассмотрим следующий код: | ||
+ | |||
+ | <source lang="cpp"> | ||
+ | int diff(int a, int b) { | ||
+ | return a - b; | ||
+ | } | ||
+ | |||
+ | template<typename T> | ||
+ | typename T::diff_type diff(T a, T b) { | ||
+ | return a - b; | ||
+ | } | ||
+ | </source> | ||
+ | |||
+ | При вызове <code>diff(3, 4)</code> компилятор попытается создать экземпляр | ||
+ | функции <code>diff<int>(int, int)</code>, но это приведет к ошибке компиляции, | ||
+ | поскольку <code>int::diff_type</code> не определено. Но эта ошибка не приводит | ||
+ | к выдаче сообщения об ошибке и прекращению компиляции, поскольку есть нешаблонная | ||
+ | функция c подходящей сигнатурой. | ||
+ | |||
+ | ---- | ||
+ | |||
+ | С помощью SFINAE можно управлять разрешением перегруженных шаблонных функций. | ||
+ | В библиотеке Boost есть шаблонный тип [http://www.boost.org/doc/libs/1_46_1/libs/utility/enable_if.html <code>enable_if_c</code>], | ||
+ | который позволяет решить эту задачу. Он определен следующим образом: | ||
+ | |||
+ | <source lang="cpp"> | ||
+ | template <bool B, 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 : public enable_if_c<Cond::value, T> {}; | ||
+ | </source> | ||
+ | |||
+ | Таким образом, конструкция <code>enable_if_c<false, T>::type</code> вызовет ошибку компиляции. | ||
+ | <code>enable_if</code> ведет себя так же, как и <code>enable_if_c</code>, но более удобен при | ||
+ | использовании trait'ов: | ||
+ | |||
+ | <source lang="cpp"> | ||
+ | template <class T, class Enable = void> | ||
+ | class A { ... }; | ||
+ | |||
+ | template <class T> | ||
+ | class A<T, typename enable_if<is_integral<T> >::type> { ... }; | ||
+ | |||
+ | template <class T> | ||
+ | class A<T, typename enable_if<is_float<T> >::type> { ... }; | ||
+ | </source> | ||
+ | |||
+ | В этом примере <code>enable_if</code> позволяет выбирать конкретную специализацию | ||
+ | шаблона класса <code>A</code> в зависимости от того, является ли тип <code>T</code> | ||
+ | целочисленным или вещественнозначным. | ||
== Проверка наличия метода у класса == | == Проверка наличия метода у класса == | ||
+ | |||
+ | В следующем примере показано, как с помощью шаблонов можно проверить | ||
+ | наличие метода <code>size</code> у класса. | ||
+ | |||
+ | <source lang="cpp"> | ||
+ | #include <iostream> | ||
+ | #include <vector> | ||
+ | |||
+ | typedef char true_type; | ||
+ | class false_type { true_type a[2]; }; | ||
+ | |||
+ | template<typename T, size_t (T::*)() const> | ||
+ | struct wrap { }; | ||
+ | |||
+ | template<typename T> | ||
+ | true_type check(T*, wrap<T, &T::size> = wrap<T, &T::size>()) { } | ||
+ | |||
+ | template<typename T> | ||
+ | false_type check(void*, ...) { } | ||
+ | |||
+ | template<typename T> | ||
+ | struct check_size { | ||
+ | static const bool value = sizeof(check<T>((T*)0)) == sizeof(true_type); | ||
+ | }; | ||
+ | |||
+ | class bar { | ||
+ | void no_size() { } | ||
+ | }; | ||
+ | |||
+ | int main() { | ||
+ | if (check_size<std::vector<int> >::value == true) { | ||
+ | std::cout << "Vector has a size field!" << std::endl; | ||
+ | } | ||
+ | |||
+ | if (check_size<bar>::value != true) { | ||
+ | std::cout << "Bar doesn't have a size field!" << std::endl; | ||
+ | } | ||
+ | } | ||
+ | </source> | ||
+ | |||
+ | При создании экземпляра класса <code>check_size<std::vector<int></code> создается | ||
+ | первый вариант функции <code>check_size</code>, поскольку можно без проблем создать | ||
+ | экземпляр структуры <code>wrap</code>. | ||
+ | |||
+ | При создании экземпляра класса <code>check_size<bar></code> создается второй вариант | ||
+ | функции <code>check_size</code>, поскольку создание экземпляра структуры <code>wrap</code> | ||
+ | приводит к ошибке. | ||
== Списки типов == | == Списки типов == | ||
+ | На шаблонах можно реализовать списки в функциональном стиле. Правда, такие списки | ||
+ | будут существовать только во время компиляции. | ||
+ | |||
+ | Сначала реализуем общее описание списка: | ||
+ | <source lang="cpp"> | ||
+ | struct nil { }; | ||
+ | |||
+ | template<typename F, typename S = nil> | ||
+ | struct cons { | ||
+ | typedef F head; | ||
+ | typedef S tail; | ||
+ | }; | ||
+ | </source> | ||
+ | |||
+ | Тогда список из трех элементов можно описать так: | ||
+ | |||
+ | <source lang="cpp"> | ||
+ | typedef cons<A, cons<B, cons<C> > type_list; | ||
+ | </source> | ||
+ | |||
+ | Можно унаследовать класс от всех классов, входящих в список: | ||
+ | |||
+ | <source lang="cpp"> | ||
+ | template<typename L> | ||
+ | struct inherit : L::head, inherit<typename L::tail> { }; | ||
+ | |||
+ | template<> | ||
+ | struct inherit<nil> { }; | ||
+ | |||
+ | struct D : inherit<type_list>; | ||
+ | </source> | ||
+ | |||
+ | Структура <code>D</code> будет унаследована от структур <code>A</code>, | ||
+ | <code>B</code> и <code>C</code>. | ||
+ | |||
+ | '''Задача''' Найти способ вызвать общую функцию у всех родителей. | ||
+ | |||
+ | '''Задача''' Реализовать красно-черные деревья на шаблонах :). | ||
+ | |||
+ | == Поддержка метапрограммирования в Boost == | ||
+ | |||
+ | Вот список модулей библиотеки Boost, реализующих некоторые фичи (sic!) | ||
+ | метапрограммирования: | ||
+ | |||
+ | * [http://www.boost.org/doc/libs/1_46_1/libs/utility/call_traits.htm call_traits], | ||
+ | * [http://www.boost.org/doc/libs/1_46_1/libs/concept_check/concept_check.htm concept check], | ||
+ | * [http://www.boost.org/doc/libs/1_46_1/libs/utility/enable_if.html enable_if], | ||
+ | * [http://www.boost.org/doc/libs/1_46_1/libs/function_types/doc/html/index.html function_types], | ||
+ | * [http://www.boost.org/doc/libs/1_46_1/libs/utility/in_place_factories.html in_place_factory, typed_in_place_factory], | ||
+ | * [http://www.boost.org/doc/libs/1_46_1/libs/utility/operators.htm operators], | ||
+ | * [http://www.boost.org/doc/libs/1_46_1/libs/property_map/doc/property_map.html property map], | ||
+ | * [http://www.boost.org/doc/libs/1_46_1/doc/html/boost_staticassert.html static_assert], | ||
+ | * [http://www.boost.org/doc/libs/1_46_1/libs/type_traits/doc/html/index.html type_traits], | ||
+ | * [http://www.boost.org/doc/libs/1_46_1/libs/fusion/doc/html/index.html fusion], | ||
+ | * [http://www.boost.org/doc/libs/1_46_1/libs/mpl/doc/index.html mpl], | ||
+ | * [http://www.boost.org/doc/libs/1_46_1/doc/html/proto.html proto]. | ||
+ | |||
+ | Модули Boost, реализующие некоторые концепции языков функционального программирования: | ||
+ | |||
+ | * [http://www.boost.org/doc/libs/1_46_1/libs/bind/bind.html bind] и [http://www.boost.org/doc/libs/1_46_1/libs/bind/mem_fn.html mem_fn], | ||
+ | * [http://www.boost.org/doc/libs/1_46_1/doc/html/lambda.html lambda], | ||
+ | * [http://www.boost.org/doc/libs/1_46_1/doc/html/function.html function], | ||
+ | * [http://www.boost.org/doc/libs/1_46_1/libs/tuple/doc/tuple_users_guide.html tuple] | ||
+ | |||
+ | Модули, входящие в состав модуля MPL: | ||
+ | |||
+ | * [http://www.boost.org/doc/libs/1_46_1/libs/mpl/doc/refmanual/classes.html Метаконтейнеры] --- аналоги контейнеров STL, существующие только в момент компиляции | ||
+ | * [http://www.boost.org/doc/libs/1_46_1/libs/mpl/doc/refmanual/concepts.html Метаитераторы] --- аналоги итераторов STL, работающие с метаконтейнерами: | ||
+ | * [http://www.boost.org/doc/libs/1_46_1/libs/mpl/doc/refmanual/intrinsic-metafunctions.html Метафункции] и [http://www.boost.org/doc/libs/1_46_1/libs/mpl/doc/refmanual/algorithms.html метаалгоритмы], работающие с метаконтейнерами. | ||
+ | * [http://www.boost.org/doc/libs/1_46_1/libs/mpl/doc/refmanual/introspection.html Статическая интроспекция], позволяющая определить наличие определения псевдонима типа внутри класса. | ||
+ | * [http://www.boost.org/doc/libs/1_46_1/libs/mpl/doc/refmanual/asserts.html Статический Assert] --- аналог <code>BOOST_STATIC_ASSERT</code> с более подробным сообщением об ошибке (как заверяют разработчики). | ||
+ | |||
+ | === Пример === | ||
+ | |||
+ | Вот простейший пример использования метаконтейнеров: | ||
+ | |||
+ | <source lang="cpp"> | ||
+ | #include <boost/mpl/vector.hpp> | ||
+ | #include <boost/mpl/int.hpp> | ||
+ | #include <iostream> | ||
+ | #include <typeinfo> | ||
+ | #include <vector> | ||
+ | |||
+ | using namespace boost; | ||
+ | |||
+ | typedef mpl::vector<int, mpl::int_<1>, double> numbers; | ||
+ | |||
+ | int main() { | ||
+ | std::cout << mpl::v_at<numbers, 1>::type::value << std::endl; // Выводит 1 | ||
+ | std::cout << mpl::v_at<mpl::push_back<numbers, mpl::int_<2> >::type, 3>::type::value << std::endl; // Выводит 2 | ||
+ | } | ||
+ | </source> | ||
+ | |||
+ | Осмысленный пример использования метаконтейнеров --- реализация концепции единиц измерения на С++: | ||
+ | |||
+ | <source lang="cpp"> | ||
+ | using namespace boost; | ||
+ | |||
+ | template<int kg, int m, int s> | ||
+ | struct unit { | ||
+ | typedef mpl::vector<mpl::int_<kg>, mpl::int_<m>, mpl::int_<s> > u; | ||
+ | |||
+ | float value; | ||
+ | } | ||
+ | |||
+ | typedef unit<1, 0, 0> mass; | ||
+ | typedef unit<0, 1, -1> velocity; | ||
+ | typedef unit<0, 1, -2> acceleration; | ||
+ | typedef unit<1, 1, -1> momentum; | ||
+ | </source> | ||
+ | |||
+ | [[Файл:Swoon2.gif]] |
Текущая версия на 01:35, 25 мая 2012
Мы рассмотрим несколько <<необычных>> примеров использования шаблонов C++.
Содержание
Статический assert
В следующем примере приведен код, который компилируется только на 64-разрядной платформе:
char a[sizeof(int*) == 8 ? 1 : -1];
Если код компилируется не на 64-разрядной платформе, то
sizeof(int*) != 8
, что приведет к объявлению массива a отрицательного
размера, а это запрещено стандартом.
Эта идея используется в макросе BOOST_STATIC_ASSERT
, предоставляемом
модулем Static Assert
библиотеки Boost.
Tag passing
Предположим, нам нужно написать функцию, которая циклически переставляет элементы массива:
template<typename It>
void rotate(It p, It, m, It q);
где p
, q
--- итераторы, указывающие на начало и конец массива,
а элемент, на который указывает m
, после завершения работы функции будет располагаться
на месте элемента, на который указывает p
.
Допустим, у нас есть разные реализации этой функции для разных типов итераторов:
template<typename It>
void rotate_bidirectional(It p, It, m, It q);
...
template<typename It>
void rotate_random_access(It p, It, m, It q);
Мы можем добавить к сигнатуре этих функций формальный параметр и перенести информацию о типе итератора, с которым работает эта функция, из ее имени в этот параметр:
template<typename It>
void rotate(It p, It, m, It q, bidirectional_tag);
...
template<typename It>
void rotate(It p, It, m, It q, random_access_tag);
Тогда исходную функцию можно реализовать так:
template<typename It>
void rotate(It p, It, m, It q) {
rotate(p, m, q, iterator_traits<It>::iterator_category());
}
Замена числовых идентификаторов на типы
С помощью следующего трюка можно переписать функцию, поведение которой зависит от числового идентификатора, так, чтобы ее поведение зависело от формального параметра (как в предыдущем разделе):
void foo(int);
Мы можем определить шаблонную структуру:
template<int i>
struct int2type { static const int value = i; };
Теперь функцию foo можно переписать так:
template<int i>
void foo(int2type<i>);
Substitution Failure Is Not An Error (SFINAE)
При создании экземпляров шаблонных функций могут возникать ошибки компиляции. Рассмотрим следующий код:
int diff(int a, int b) {
return a - b;
}
template<typename T>
typename T::diff_type diff(T a, T b) {
return a - b;
}
При вызове diff(3, 4)
компилятор попытается создать экземпляр
функции diff<int>(int, int)
, но это приведет к ошибке компиляции,
поскольку int::diff_type
не определено. Но эта ошибка не приводит
к выдаче сообщения об ошибке и прекращению компиляции, поскольку есть нешаблонная
функция c подходящей сигнатурой.
С помощью SFINAE можно управлять разрешением перегруженных шаблонных функций.
В библиотеке Boost есть шаблонный тип enable_if_c
,
который позволяет решить эту задачу. Он определен следующим образом:
template <bool B, 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 : public enable_if_c<Cond::value, T> {};
Таким образом, конструкция enable_if_c<false, T>::type
вызовет ошибку компиляции.
enable_if
ведет себя так же, как и enable_if_c
, но более удобен при
использовании trait'ов:
template <class T, class Enable = void>
class A { ... };
template <class T>
class A<T, typename enable_if<is_integral<T> >::type> { ... };
template <class T>
class A<T, typename enable_if<is_float<T> >::type> { ... };
В этом примере enable_if
позволяет выбирать конкретную специализацию
шаблона класса A
в зависимости от того, является ли тип T
целочисленным или вещественнозначным.
Проверка наличия метода у класса
В следующем примере показано, как с помощью шаблонов можно проверить
наличие метода size
у класса.
#include <iostream>
#include <vector>
typedef char true_type;
class false_type { true_type a[2]; };
template<typename T, size_t (T::*)() const>
struct wrap { };
template<typename T>
true_type check(T*, wrap<T, &T::size> = wrap<T, &T::size>()) { }
template<typename T>
false_type check(void*, ...) { }
template<typename T>
struct check_size {
static const bool value = sizeof(check<T>((T*)0)) == sizeof(true_type);
};
class bar {
void no_size() { }
};
int main() {
if (check_size<std::vector<int> >::value == true) {
std::cout << "Vector has a size field!" << std::endl;
}
if (check_size<bar>::value != true) {
std::cout << "Bar doesn't have a size field!" << std::endl;
}
}
При создании экземпляра класса check_size<std::vector<int>
создается
первый вариант функции check_size
, поскольку можно без проблем создать
экземпляр структуры wrap
.
При создании экземпляра класса check_size<bar>
создается второй вариант
функции check_size
, поскольку создание экземпляра структуры wrap
приводит к ошибке.
Списки типов
На шаблонах можно реализовать списки в функциональном стиле. Правда, такие списки будут существовать только во время компиляции.
Сначала реализуем общее описание списка:
struct nil { };
template<typename F, typename S = nil>
struct cons {
typedef F head;
typedef S tail;
};
Тогда список из трех элементов можно описать так:
typedef cons<A, cons<B, cons<C> > type_list;
Можно унаследовать класс от всех классов, входящих в список:
template<typename L>
struct inherit : L::head, inherit<typename L::tail> { };
template<>
struct inherit<nil> { };
struct D : inherit<type_list>;
Структура D
будет унаследована от структур A
,
B
и C
.
Задача Найти способ вызвать общую функцию у всех родителей.
Задача Реализовать красно-черные деревья на шаблонах :).
Поддержка метапрограммирования в Boost
Вот список модулей библиотеки Boost, реализующих некоторые фичи (sic!) метапрограммирования:
- call_traits,
- concept check,
- enable_if,
- function_types,
- in_place_factory, typed_in_place_factory,
- operators,
- property map,
- static_assert,
- type_traits,
- fusion,
- mpl,
- proto.
Модули Boost, реализующие некоторые концепции языков функционального программирования:
Модули, входящие в состав модуля MPL:
- Метаконтейнеры --- аналоги контейнеров STL, существующие только в момент компиляции
- Метаитераторы --- аналоги итераторов STL, работающие с метаконтейнерами:
- Метафункции и метаалгоритмы, работающие с метаконтейнерами.
- Статическая интроспекция, позволяющая определить наличие определения псевдонима типа внутри класса.
- Статический Assert --- аналог
BOOST_STATIC_ASSERT
с более подробным сообщением об ошибке (как заверяют разработчики).
Пример
Вот простейший пример использования метаконтейнеров:
#include <boost/mpl/vector.hpp>
#include <boost/mpl/int.hpp>
#include <iostream>
#include <typeinfo>
#include <vector>
using namespace boost;
typedef mpl::vector<int, mpl::int_<1>, double> numbers;
int main() {
std::cout << mpl::v_at<numbers, 1>::type::value << std::endl; // Выводит 1
std::cout << mpl::v_at<mpl::push_back<numbers, mpl::int_<2> >::type, 3>::type::value << std::endl; // Выводит 2
}
Осмысленный пример использования метаконтейнеров --- реализация концепции единиц измерения на С++:
using namespace boost;
template<int kg, int m, int s>
struct unit {
typedef mpl::vector<mpl::int_<kg>, mpl::int_<m>, mpl::int_<s> > u;
float value;
}
typedef unit<1, 0, 0> mass;
typedef unit<0, 1, -1> velocity;
typedef unit<0, 1, -2> acceleration;
typedef unit<1, 1, -1> momentum;