Метапрограммирование на C++ — различия между версиями

Материал из SEWiki
Перейти к: навигация, поиск
м
(Substitution Failure Is Not An Error (SFINAE))
 
(не показаны 32 промежуточные версии 2 участников)
Строка 1: Строка 1:
Мы рассмотрим несколько техник использования шаблонов C++.
+
Мы рассмотрим несколько <<необычных>> примеров использования шаблонов C++.
  
 
== Статический <code>assert</code> ==
 
== Статический <code>assert</code> ==
Строка 6: Строка 6:
  
 
<source lang="cpp">
 
<source lang="cpp">
char[sizeof(int*)] == 8 ? 1 : -1]
+
char a[sizeof(int*) == 8 ? 1 : -1];
 
</source>
 
</source>
  
Строка 14: Строка 14:
  
 
Эта идея используется в макросе <code>BOOST_STATIC_ASSERT</code>, предоставляемом
 
Эта идея используется в макросе <code>BOOST_STATIC_ASSERT</code>, предоставляемом
модулем <code>Static Assert</code> библиотеки <code>Boost</code>.
+
модулем [http://www.boost.org/doc/libs/1_46_1/doc/html/boost_staticassert.html <code>Static Assert</code>]
 +
библиотеки Boost.
  
 
== Tag passing ==
 
== Tag passing ==
Строка 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; };
  static const int value = i;
+
}
+
 
</source>
 
</source>
  
Строка 89: Строка 88:
 
</source>
 
</source>
  
== Substitution Failure Is Not An Error (SWINAE) ==
+
== 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!) метапрограммирования:

Модули Boost, реализующие некоторые концепции языков функционального программирования:

Модули, входящие в состав модуля MPL:

Пример

Вот простейший пример использования метаконтейнеров:

#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;

Swoon2.gif