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

Материал из SEWiki
Перейти к: навигация, поиск
м
(Substitution Failure Is Not An Error (SFINAE))
 
(не показано 25 промежуточных версий 2 участников)
Строка 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 ==
Строка 88: Строка 89:
  
 
== Substitution Failure Is Not An Error (SFINAE) ==
 
== Substitution Failure Is Not An Error (SFINAE) ==
При создании экземпляров шаблонных функций могут возникать компиляции.
+
При создании экземпляров шаблонных функций могут возникать ошибки компиляции.
 
Рассмотрим следующий код:
 
Рассмотрим следующий код:
  
Строка 107: Строка 108:
 
к выдаче сообщения об ошибке и прекращению компиляции, поскольку есть нешаблонная
 
к выдаче сообщения об ошибке и прекращению компиляции, поскольку есть нешаблонная
 
функция c подходящей сигнатурой.
 
функция 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>
 +
целочисленным или вещественнозначным.
  
 
== Проверка наличия метода у класса ==
 
== Проверка наличия метода у класса ==
Строка 127: Строка 166:
  
 
template<typename T>
 
template<typename T>
false_type check(void*) { }
+
false_type check(void*, ...) { }
  
 
template<typename T>
 
template<typename T>
Строка 156: Строка 195:
 
функции <code>check_size</code>, поскольку создание экземпляра структуры <code>wrap</code>
 
функции <code>check_size</code>, поскольку создание экземпляра структуры <code>wrap</code>
 
приводит к ошибке.
 
приводит к ошибке.
 
''Замечание'' Я не совсем понял, почему нельзя первый вариант функции <code>check_size</code>
 
написать так:
 
<source lang="cpp">
 
template<typename T>
 
true_type check(T*, size_t (T::*)() const = &T::size) { };
 
</source>
 
 
При создании экземпляра класса <code>check_size<bar></code> компилятор говорит, что
 
у класса <code>bar</code> нет метода <code>size</code>. В этом случае SFINAE не работает?
 
  
 
== Списки типов ==
 
== Списки типов ==
Строка 204: Строка 233:
  
 
'''Задача''' Найти способ вызвать общую функцию у всех родителей.
 
'''Задача''' Найти способ вызвать общую функцию у всех родителей.
 +
 +
'''Задача''' Реализовать красно-черные деревья на шаблонах :).
 +
 +
== Поддержка метапрограммирования в 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