1 апр. 2009 г.

Использование характеристик типов boost/type_traits.hpp


Содержание


Вступление

Содержимое (классы) заголовочного файла <boost/type_traits.hpp> объявляется в пространстве имен boost.

В файле <boost/type_traits.hpp> определены три вида свойств типов:

  • свойства выбранного типа
  • отношения между двумя типами
  • преобразования от одного типа к другому

Если Вы ранее не работали с этой библиотекой, то предлагаем ознакомиться со статьей, где описываются основы и цели создания библиотеки.

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

Первичная категоризация типов

Нижеследующие шаблоны свойств типов позволяют определить, к какой категории типов принадлежит заданный тип. Для любого заданного типа только одно выражение получит значение true. Заметьте, что is_integral<T>::value и is_float<T>::value будут true только для встроенных типов; если есть необходимость проверки для пользовательских типов так же, как для встроенных, то следует использовать шаблон std::numeric_limits.

  • ::boost::is_void<T>::value
    становится true только если T является cv-qualified void типом.
  • ::boost::is_float<T>::value
    становится true только если T является cv-qualified типом с плавающей точкой
  • ::boost::is_integral<T>::value
    становится true только если T является cv-qualified целым типом.
  • ::boost::is_float<T>::value
    становится true только если T является cv-qualified типом с плавающей точкой
  • ::boost::is_pointer<T>::value
    становится true только если T является cv-qualified указателем (включая указатели на функции, но исключая указатели на члены классов).
  • ::boost::is_reference<T>::value
    становится true только если T является ссылкой.
    если компилятор не поддерживает частичную специализацию шаблонов классов, тогда этот шаблон может давать неправильные результаты.
  • ::boost::is_member_pointer<T>::value
    становится true только если T является a cv-qualified указателем на член класса (поле класса или метод).
  • ::boost::is_array<T>::value
    становится true только если T является массивом.
    если компилятор не поддерживает частичную специализацию шаблонов классов, тогда этот шаблон может давать неправильные результаты.
  • ::boost::is_union<T>::value
    становится true только если T является типом union. В настоящее время требуется некоторая поддержка со стороны компилятора, иначе union'ы рассматриваются как классы.
    Без определенной помощи со стороны компилятора (в настоящее время данная возможность не стандартизована), мы не можем отличить union от класса, в результате чего выражение никогда не получается true.
  • ::boost::is_class<T>::value
    становится true только если T является классом или структурой.
    Без определенной помощи со стороны компилятора (в настоящее время данная возможность не стандартизована), мы не можем отличать union от классов, в результате чего выражение дает неправильный ответ true для аргумента типа union.
  • ::boost::is_enum<T>::value
    становится true только если T является перечислением (enum).
    требуется правильно работающий шаблон is_convertible (это означает, что шаблон is_enum в настоящее время не работает в Borland C++ Builder 5, и в некоторых версиях компилятора Metrowerks).
  • ::boost::is_function<T>::value
    становится true только если T является функциональным типом (но не ссылкой и не указателем на функцию).
    если компилятор не поддерживает частичную специализацию шаблонов классов, тогда этот шаблон не компилируется для ссылочных типов.

Свойства типов

Нижеописанные шаблоны идентифицируют свойства, которые имеет заданный тип.

  • ::boost::alignment_of<T>::value
    идентифицирует величину выравнивания для T. в действительности возвращает величину, которая гарантированно является множителем действительной величины выравнивания. T должен быть полностью определен (complete type)
  • ::boost::is_empty<T>::value
    true если T является пустой структурой или классом. Если компилятор поддерживает метод оптимизации "пустые базовые классы нулевого размера", тогда шаблон is_empty может правильно определить, что T пустой. Используется шаблон is_class для определения, когда T является классом.
    T должен быть полностью определен (complete type)
    Компилятор должен поддерживать реализацию пустых базовых классов нулевого размера, чтобы обнаружение пустых классов работало.
    Не может быть использован с неопределенными полностью типами.
    Не может работать с union'ами до тех пор, пока шаблон is_union сможет работать (см. его описание).
    Если компилятор не поддерживает частичную специализацию шаблонов классов, тогда этот шаблон не может быть использован с абстрактными типами.
  • ::boost::is_const<T>::value
    true только если  T квалифицирован как const (top-level const-qualified).
  • ::boost::is_volatile<T>::value
    true только если  T квалифицирован как volatile.
  • ::boost::is_polymorphic<T>::value
    true только если  T является полиморфным типом.
    T должен быть полностью определен (complete type)
    Требуется знание ABI компилятора, в действительности шаблон работоспособен на большинстве существующих компиляторов.
  • ::boost::is_pod<T>::value
    true только если  T является cv-qualified POD, то есть простой структурой без методов, с возможными квалификаторами const и volatile.
    T должен быть полностью определен (complete type).
    Без некоторой помощи (в настоящее время эти возможности не стандартизованы) со стороны компилятора, шаблон is_pod не может определить, что T является POD; это безопасно, хотя может быть не оптимальным.
    Если компилятор не поддерживает частичную специализацию шаблонов классов, то данный шаблон не может быть использован с функциональными типами.
  • ::boost::has_trivial_constructor<T>::value
    true если T имеет простой деструктор, сгенерированный компилятором (trivial default constructor).
    Без некоторой помощи (в настоящее время эти возможности не стандартизованы) со стороны компилятора, шаблон has_trivial_constructor не сможет определить, что заданный тип имеет пустой конструктор. Это всегда безопасно, хотя может быть неоптимальным.
    Если компилятор не поддерживает частичную специализацию шаблонов классов, то данный шаблон не может быть использован с функциональными типами.
  • ::boost::has_trivial_copy<T>::value
    true если T имеет простой сгенерированный компилятором конструктор копирования ( trivial copy constructor).
    T должен быть полностью определен (complete type).
    Без некоторой помощи (в настоящее время эти возможности не стандартизованы) со стороны компилятора, шаблон has_trivial_copy  не сможет определить, что заданный тип имеет пустой конструктор копирования. Это всегда безопасно, хотя может быть неоптимальным.
    Если компилятор не поддерживает частичную специализацию шаблонов классов, то данный шаблон не может быть использован с функциональными типами.
  • ::boost::has_trivial_assign<T>::value
    true если T имеет простой сгенерированный компилятором оператор присваивания (trivial assignment operator).
    T должен быть полностью определен (complete type).
    Без некоторой помощи (в настоящее время эти возможности не стандартизованы) со стороны компилятора, шаблон has_trivial_assign  не сможет определить, что заданный тип имеет пустой оператор присваивания. Это всегда безопасно, хотя может быть неоптимальным.
    Если компилятор не поддерживает частичную специализацию шаблонов классов, то данный шаблон не может быть использован с функциональными типами.
  • ::boost::has_trivial_destructor<T>::value
    true если T имеет простой, сгенерированный деструктор (trivial destructor).
    T должен быть полностью определен (complete type).
    Без некоторой помощи (в настоящее время эти возможности не стандартизованы) со стороны компилятора, шаблон  has_trivial_destructor не сможет определить, что заданный тип имеет пустой оператор присваивания. Это всегда безопасно, хотя может быть неоптимальным.
    Если компилятор не поддерживает частичную специализацию шаблонов классовв, то данный шаблон не может быть использован с функциональными типами.
  • ::boost::is_stateless<T>::value
    true если T не имеет состояния, то есть T не выделяет память и его конструкторы и деструктор сгенерированы компилятором.
    T должен быть полностью определен (complete type).
    Без некоторой помощи (в настоящее время эти возможности не стандартизованы) со стороны компилятора, шаблон is_stateless не сможет определить, что заданный тип не имеет состояния. Это всегда безопасно, хотя может быть неоптимальным.
    Выражение становится true только если становятся true все нижеуказанные:
    ::boost::has_trivial_constructor<T>::value,
    ::boost::has_trivial_copy<T>::value,
    ::boost::has_trivial_destructor<T>::value,
    ::boost::is_class<T>::value,
    ::boost::is_empty<T>::value
    Если компилятор не поддерживает частичную специализацию шаблонов классов, то данный шаблон не может быть использован с функциональными типами.
  • ::boost::has_nothrow_constructor<T>::value
    true если T имеет конструктор по умолчанию, не генерирующий исключения.
    T должен быть полностью определен (complete type).
    Без некоторой помощи (в настоящее время эти возможности не стандартизованы) со стороны компилятора, шаблон  has_nothrow_constructor не сможет определить, что заданный тип имеет конструктор, не генерирующий исключения. Это всегда безопасно, хотя может быть неоптимальным.
    Если компилятор не поддерживает частичную специализацию шаблонов классов, то данный шаблон не может быть использован с функциональными типами.
  • ::boost::has_nothrow_copy<T>::value
    true если T имеет конструктор копирования, не генерирущий исключений.
    T должен быть полностью определен (complete type).
    Без некоторой помощи (в настоящее время эти возможности не стандартизованы) со стороны компилятора, шаблон has_nothrow_copy не сможет определить, что заданный тип имеет конструктор копирования, не генерирующий исключения. Это всегда безопасно, хотя может быть неоптимальным.
    Если компилятор не поддерживает частичную специализацию шаблонов классов, то данный шаблон не может быть использован с функциональными типами.
  • ::boost::has_nothrow_assign<T>::value
    true если T имеет оператор присваивания, не генерирующий исключений.
    T должен быть полностью определен (complete type).
    Без некоторой помощи (в настоящее время эти возможности не стандартизованы) со стороны компилятора, шаблон  has_nothrow_assign не сможет определить, что заданный тип имеет оператор присваивания, не генерирующий исключения. Это всегда безопасно, хотя может быть неоптимальным.
    Если компилятор не поддерживает частичную специализацию шаблонов классов, то данный шаблон не может быть использован с функциональными типами.

Отношения между типами

Следующая таблица содержит шаблоны, с помощью которых можно выяснить, есть ли какая-либо зависимость между двумя типами:

  • ::boost::is_same<T,U>::value
    становится true, если T и U - один и тот же тип.
    Если компилятор не поддерживает частичную специализацию шаблонов классов, тогда этот шаблон не может быть использован с  абстрактными, неполностью определенными или функциональными типами.
  • ::boost::is_convertible<T,U>::value
    становится true, если воображаемый объект типа  T может быт преобразован в тип U.
    Тип T не должен быть неполностью определенным типом.
    Тип U не должен быть неполностью определенным, либо абстрактным типом, либо функциональным типом.
    Подразумевается, что никакой тип не может быть преобразован в массив.
    Обратите внимание, что данный шаблон работает неправильно с Borland C++ Builder 5 (и более ранними версиями) для преобразований через конструкторы, для с компилятором Metrowerks 7 (и более ранними версиями) во всех случаях.
  • ::boost::is_base_and_derived<T,U>::value
    становится true, если T является базовым классом для типа U.
    Будут обнаружены не-публичные базовые классы, и неоднозначные базовые классы.
    Обратите внимание, что любой класс не рассматривается как собственный базовый класс. Аналогично, если T или U не являются классами, тогда результат будет в любом случае false.
    Типы T и U не должны быть неполностью описанными.
    Если компилятор не поддерживает частичную специализацию шаблонов классов, тогда этот шаблон не может быть использован с функциональными типами.

Обратите внимание, что как is_convertible, так и is_base_and_derived могут привести к ошибке компиляции, если преобразование неоднозначно.:

struct A {};
struct B : A {};
struct C : A {};
struct D : B, C {};
bool const x = boost::is_base_and_derived<A,D>::value;  // ошибка
bool const y = boost::is_convertible<D*,A*>::value;     // ошибка

Преобразования между типами

Следующие шаблоны преобразовывают один тип в другой, основываясь на простом правиле: каждый шаблон имеет единственный член type, который является результатом применения преобразования к аргументу шаблона T:

  • ::boost::remove_const<T>::type
    Создает тип такой же как T, но убирает квалификатор const самого верхнего уровня. К примеру, "const int" становится"int", но "const int*" остается неизменным.
    Если компилятор не поддерживает частичную специализацию шаблонов классов, тогда этот шаблон компилируется, но не работает, за исключением случаев, указанных в примечании ниже.
  • ::boost::remove_volatile<T>::type
    Создает тип такой же как T, но убирает квалификатор volatile самого верхнего уровня. К примеру, "volatile int" становится "int".
    Если компилятор не поддерживает частичную специализацию шаблонов классов, тогда этот шаблон компилируется, но не работает, за исключением случаев, указанных в примечании ниже.
  • ::boost::remove_cv<T>::type
    Создает тип такой же как T, но убирает квалификаторы const и volatile самого верхнего уровня. К примеру "const volatile int" становится "int".
    Если компилятор не поддерживает частичную специализацию шаблонов классов, тогда этот шаблон компилируется, но не работает, за исключением случаев, указанных в примечании ниже.
  • ::boost::remove_reference<T>::type
    Если T является ссылкой, тогда удаляет ссылку, иначе тип T остается неизменным. К примеру, "int&" становится "int", но "int*" не изменится.
    Если компилятор не поддерживает частичную специализацию шаблонов классов, тогда этот шаблон компилируется, но не работает, за исключением случаев, указанных в примечании ниже.
  • ::boost::remove_bounds<T>::type
    Если T является типом массива, тогда удаляет квалификатор верхнего уровня массива, в противном случае T не изменяется. К примеру,  "int[2][3]" становится "int[3]".
    если компилятор не поддерживает частичную специализацию шаблонов классов, тогда этот шаблон компилируется, но не работает.
  • ::boost::remove_pointer<T>::type
    Если T является указателем, тогда удаляет указатель верхнего уровня (top-level indirection) у T, иначе T остается неизменным. К примеру, "int*" становится "int", но "int&" не меняется.
    Если компилятор не поддерживает частичную специализацию шаблонов классов, тогда этот шаблон компилируется, но не работает, за исключением случаев, указанных в примечании ниже.
  • ::boost::add_reference::type
    Если T является ссылкой, тогда тип T не меняется, иначе преобразует T в ссылку. К примеру, "int&" не меняется, а  "double" становится "double&".
  • ::boost::add_pointer<T>::type
    Результатом работы является тип, который получается как  remove_reference<T>::type*. К примеру, "int" и "int&" становятся "int*".
    Если компилятор не поддерживает частичную специализацию шаблонов классов, тогда этот шаблон на компилируется с ссылочными типами.
  • ::boost::add_const<T>::type
    То же, что "T const" для всех T.
  • ::boost::add_volatile<T>::type
    То же, что "T volatile" для всех T.
  • ::boost::add_cv<T>::type
    То же, что "T const volatile" для всех T.

Как видно по данной таблице, поддержка частичной специализации шаблонов классов необходима для правильной реализации шаблоном преобразования типов. С другой стороны, практика показывает, что многие шаблоны из данной категории очень полезны, и зачастую жизненно необходимы для реализации некоторых обобщенных библиотек (generic libraries). Отсутствие этих шаблонов часто является одним из главных ограничивающих факторов в портировании таких библиотек для компиляторов, которые все еще не поддерживают необходимые возможности языка C++. Поскольку некоторые из этих компиляторов будут использоваться еще какое-то время, а по крайней мере один из них широко распространен, было решено, что библиотека должна содержать решение проблемы, когда это возможно. Основной способ решения такой:

  1. Вручную создать полные специализации шаблонов для всех типов преобразований для всех фундаментальных типов, а также всех их указателей (* и **) с/без квалификаторами const и volatile
  2. Создать пользовательские макросы, которые будут определять такие явные специализации для любого пользовательского типа  T.

Первый пункт гарантирует успешную компиляцию такого фрагмента:

BOOST_STATIC_ASSERT((is_same<char, remove_reference<char&>::type>::value));
BOOST_STATIC_ASSERT((is_same<char const, remove_reference<char const&>::type>::value));
BOOST_STATIC_ASSERT((is_same<char volatile, remove_reference<char volatile&>::type>::value));
BOOST_STATIC_ASSERT((is_same<char const volatile, remove_reference<char const volatile&>::type>::value));
BOOST_STATIC_ASSERT((is_same<char*, remove_reference<char*&>::type>::value));
BOOST_STATIC_ASSERT((is_same<char const*, remove_reference<char const*&>::type>::value));
....
BOOST_STATIC_ASSERT((is_same<char const volatile* const volatile* const volatile, remove_reference<char const volatile* const volatile* const volatile&>::type>::value));

и второй пункт обеспечивает для пользователей библиотеки механизм, с помощью которого вышеприведенный код будет работать не только для типов 'char', 'int' или других встроенных типов, но для любых пользовательских типов:

struct my {};
BOOST_BROKEN_COMPILER_TYPE_TRAITS_SPECIALIZATION(my)
BOOST_STATIC_ASSERT((is_same<my, remove_reference<my&>::type>::value));
BOOST_STATIC_ASSERT((is_same<my, remove_const<my const>::type>::value));
// etc.

Заметьте, что макрос BOOST_BROKEN_COMPILER_TYPE_TRAITS_SPECIALIZATION становится пустым для компиляторов, которые не поддерживают частичную специализацию.

Синтезирование типов

Следующие шаблоны синтезируют типы с желаемыми свойствами.

  • ::boost::type_with_alignment<Align>::type
  • пытается найти встроенный тип или POD, который имеет значение выравнивания такое же, как множитель заданного параметр а Align.

Свойства функций

Шаблон класса ::boost::function_traits извлекает информацию о функциональном типе.

  • ::boost::function_traits<F>::arity
    определяет арность функционального типа F.
    Без поддержки частичной специализации данный шаблон не компилируется для ссылочных типов.
  • ::boost::function_traits<F>::result_type
    тип, возвращаемый функциональным типом  F.
    Не компилируется без поддержки частичной специализации шаблонов классов.
  • ::boost::function_traits<F>::argN_type
    тип Nго аргумента функции F, где 1<=N<=арность(F).
    Не компилируется без поддержки частичной специализации шаблонов классов.

Заголовочные файлы свойств типов

Библиотека свойств типов нормально подключается заголовочным файлом:

#include <boost/type_traits.hpp>

Однако в действительности библиотека разбита на ряд небольших файлов, так что иногда может быть удобнее включать один из этих файлов напрямую, чтобы получить только те шаблоны, которые нужны. Отдельные хидеры всегда имеют то же имя, что и соответствующий шаблон, и хранятся в каталоге boost/type_traits/.  Так что если, к примеру, некоторый код нуждается в шаблоне  is_class<>, тогда просто включайте:

<boost/type_traits/is_class.hpp>

Пользовательские специализации

В ряде случаев конечный пользователь может нуждаться в собственных специализациях для какого-либо из шаблонов свойств типов - обычно когда используемый компилятор требует, чтобы соответствующее свойство типа было реализовано полностью. Эти специализации должны наследовать от boost::mpl::true_ или boost::mpl::false_ соответственно:

#  include <boost/type_traits/is_pod.hpp>
#  include <boost/type_traits/is_class.hpp>
#  include <boost/type_traits/is_union.hpp>

struct my_pod{};
struct my_union
{
char c;
int i;
};

namespace boost
{
template<>
struct is_pod<my_pod> 
: public mpl::true_{};
template<>
struct is_pod<my_union> 
: public mpl::true_{};
template<>
struct is_union<my_union> 
: public mpl::true_{};
template<>
struct is_class<my_union> 
: public mpl::false_{};
}

Примеры

Есть четыре примера работы с шаблонами свойств типов:

Copy_example.cpp

Демонстрируется версия std::copy, которая использует функцию memcpy, когда это возможно, для выполнения копирования (то есть если тип - один из фундаментальных, либо POD, и т.д.);

//
// opt::copy
// same semantics as std::copy
// calls memcpy where appropiate.
//

namespace detail{

template<typename I1, typename I2>
I2 copy_imp(I1 first, I1 last, I2 out)
{
while(first != last)
{
*out = *first;
++out;
++first;
}
return out;
}

template <bool b>
struct copier
{
template<typename I1, typename I2>
static I2 do_copy(I1 first, I1 last, I2 out)
{ return copy_imp(first, last, out); }
};

template <>
struct copier<true>
{
template<typename I1, typename I2>
static I2* do_copy(I1* first, I1* last, I2* out)
{
memcpy(out, first, (last-first)*sizeof(I2));
return out+(last-first);
}
};


}

template<typename I1, typename I2>
inline I2 copy(I1 first, I1 last, I2 out)
{
typedef typename boost::remove_cv<typename std::iterator_traits<I1>::value_type>::type v1_t;
typedef typename boost::remove_cv<typename std::iterator_traits<I2>::value_type>::type v2_t;
return detail::copier<
::boost::type_traits::ice_and<
::boost::is_same<v1_t, v2_t>::value,
::boost::is_pointer<I1>::value,
::boost::is_pointer<I2>::value,
::boost::has_trivial_assign<v1_t>::value
>::value>::do_copy(first, last, out);
}

fill_example.cpp

Демонстрируется версия std::fill, которая использует функцию memset, когда это возможно, для оптимизации операции заполнения. Также используется шаблон call_traits для оптимизации передачи параметров, чтобы избежать проблем с синонимами (aliasing issues):

namespace opt{
///
// fill
// same as std::fill, uses memset where appropriate, along with call_traits
// to "optimise" parameter passing.
//
namespace detail{

template <typename I, typename T>
void do_fill_(I first, I last, typename boost::call_traits<T>::param_type val)
{
while(first != last)
{
*first = val;
++first;
}
}

template <bool opt>
struct filler
{
template <typename I, typename T>
struct rebind
{
static void do_fill(I first, I last, typename boost::call_traits<T>::param_type val)
{ do_fill_<I,T>(first, last, val); }
};
};

template <>
struct filler<true>
{
template <typename I, typename T>
struct rebind
{
static void do_fill(I first, I last, T val)
{
std::memset(first, val, last-first);
}
};
};

}

template <class I, class T>
inline void fill(I first, I last, const T& val)
{
typedef detail::filler<
::boost::type_traits::ice_and<
::boost::is_pointer<I>::value,
::boost::is_arithmetic<T>::value,
(sizeof(T) == 1)
>::value> filler_t;
typedef typename filler_t:: template rebind<I,T> binder;
binder::do_fill(first, last, val);
}
}};   // namespace opt

iter_swap_example.cpp

Демонстрируется версия алгоритма std::iter_swap, который работает с прокси-итераторами (proxying iterators), а также с обычными итераторами; вызывает  std::swap для обычных итераторов, иначе выполняет медленный, но надежный обмен:

namespace opt{
///
// iter_swap:
// tests whether iterator is a proxying iterator or not, and
// uses optimal form accordingly:
//
namespace detail{

template <bool b>
struct swapper
{
template <typename I>
static void do_swap(I one, I two)
{
typedef typename std::iterator_traits<I>::value_type v_t;
v_t v = *one;
*one = *two;
*two = v;
}
};

template <>
struct swapper<true>
{
template <typename I>
static void do_swap(I one, I two)
{
using std::swap;
swap(*one, *two);
}
};

}

template <typename I1, typename I2>
inline void iter_swap(I1 one, I2 two)
{
typedef typename std::iterator_traits<I1>::reference r1_t;
typedef typename std::iterator_traits<I2>::reference r2_t;
detail::swapper<
::boost::type_traits::ice_and<
::boost::is_reference<r1_t>::value, 
::boost::is_reference<r2_t>::value,
::boost::is_same<r1_t, r2_t>::value
>::value>::do_swap(one, two);
}

};   // namespace opt

Trivial_destructor_example.cpp

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

namespace opt{
//
// algorithm destroy_array:
// The reverse of std::unitialized_copy, takes a block of
// initialized memory and calls destructors on all objects therein.
//

namespace detail{

template <bool>
struct array_destroyer
{
template <class T>
static void destroy_array(T* i, T* j){ do_destroy_array(i, j); }
};

template <>
struct array_destroyer<true>
{
template <class T>
static void destroy_array(T*, T*){}
};

template <class T>
void do_destroy_array(T* first, T* last)
{
while(first != last)
{
first->~T();
++first;
}
}

}; // namespace detail

template <class T>
inline void destroy_array(T* p1, T* p2)
{
detail::array_destroyer<boost::has_trivial_destructor<T>::value>::destroy_array(p1, p2);
}
} // namespace opt

P.S.: Нагло утянуто отсюда: Библиотека BOOST C++: заголовочный файл <boost/type_traits.hpp>

Комментариев нет:

Отправить комментарий