Карта сайта Kansoftware
НОВОСТИУСЛУГИРЕШЕНИЯКОНТАКТЫ
Разработка программного обеспечения

enable_if

Boost , Chapter 1. Boost.Core , Chapter 1. Boost.Core

Boost C++ Libraries

...one of the most highly regarded and expertly designed C++ library projects in the world. Herb Sutter and Andrei Alexandrescu, C++ Coding Standards

PrevUpHomeNext

Authors

  • Jaakko Järvi
  • Иеремия Уиллкок
  • Эндрю Ламсдейн

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

namespace boost {
    template <class Cond, class T = void> struct enable_if;
    template <class Cond, class T = void> struct disable_if;
    template <class Cond, class T> struct lazy_enable_if;
    template <class Cond, class T> struct lazy_disable_if;
    template <bool B, class T = void> struct enable_if_c;
    template <bool B, class T = void> struct disable_if_c;
    template <bool B, class T> struct lazy_enable_if_c;
    template <bool B, class T> struct lazy_disable_if_c;
}

Чувствительная работа перегрузки функции шаблона в C++ основана на принципеSFINAE(замена-неудача-не-ошибка): Если во время инстанциации шаблона функции формируется недействительный аргумент или тип возврата, инстанциация удаляется из набора разрешения перегрузки вместо того, чтобы вызывать ошибку компиляции. Следующий пример взят из<enable_if>, показывает, почему это важно:

int negate(int i) { return -i; }
template <class F>
typename F::result_type negate(const F& f) { return -f(); }

Предположим, что компилятор встречает вызов<negate(1)>. Первое определение, очевидно, лучше, но компилятор должен, тем не менее, рассмотреть (и представить прототипы) обоих определений, чтобы выяснить это. Обоснование последнего определения<F>как<int>приведет к:

int::result_type negate(const int&);

где тип возврата недействителен. Если бы это была ошибка, добавление несвязанного шаблона функций (который никогда не назывался) могло бы нарушить действительный код. Однако из-за принципа SFINAE приведенный выше пример не является ошибочным. Последнее определение<negate>просто удаляется из набора разрешения перегрузки.

Шаблоны<enable_if>являются инструментами для контролируемого создания условий SFINAE.

Названия<enable_if>шаблонов имеют три части: факультативный<lazy_>тег, либо<enable_if>, либо<disable_if>, и факультативный<_c>тег. Поддерживаются все восемь комбинаций этих частей. Смысл<lazy_>тега описан в разделениже. Вторая часть названия указывает, должен ли аргумент истинного состояния включать или отключать текущую перегрузку. Третья часть имени указывает, является ли аргумент состояния значением<bool>(суффикс<_c>), или типом, содержащим статическую<bool>константу, названную<value>(без суффикса). Последняя версия взаимодействует с Boost. MPL.

<enable_if_c>и<enable_if>являются следующими (мы используем<enable_if>шаблоны без квалификации, но они находятся в<boost>пространстве имен).

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>с параметром<B>как<true>содержит тип члена<type>, определяемый как<T>. Если<B><false>, то такой член не определен. Таким образом,<enable_if_c<B, T>::type>является либо действительным, либо недействительным выражением типа, в зависимости от значения<B>. При этом<enable_if_c<B,T>::type>приравнивается<T>. Таким образом, шаблон<enable_if_c>может использоваться для управления, когда функции рассматриваются для разрешения перегрузки, а когда их нет. Например, для всех типов арифметики определена следующая функция (согласно классификации библиотеки Boosttype_traits):

template <class T>
typename enable_if_c<boost::is_arithmetic<T>::value, T>::type
foo(T t) { return t; }

<disable_if_c>Шаблон также предусмотрен и имеет ту же функциональность, что и<enable_if_c>, за исключением отрицаемого состояния. Для всех неарифметических типов включена следующая функция.

template <class T>
typename disable_if_c<boost::is_arithmetic<T>::value, T>::type
bar(T t) { return t; }

Для облегчения синтаксиса в некоторых случаях и взаимодействия с Boost. MPL предоставляет версии шаблонов<enable_if>, принимающие любой тип с константой<bool>членов, названной<value>в качестве аргумента условия. Для создания таких типов, вероятно, будут полезны шаблоны MPL<bool_>,<and_>,<or_>и<not_>. Кроме того, классы черт в Росте. Библиотека Type_traits следует этой конвенции. Например, вышеприведенная примерная функция<foo>может быть альтернативно записана как:

template <class T>
typename enable_if<boost::is_arithmetic<T>, T>::type
foo(T t) { return t; }

<enable_if>Шаблоны определены в<boost/utility/enable_if.hpp>, который включен в<boost/utility.hpp>.

Что касается шаблонов функций,<enable_if>может использоваться несколькими различными способами:

  • Возвратный тип мгновенной функции
  • Как дополнительный параметр инстанциированной функции
  • В качестве дополнительного параметра шаблона (полезного только в компиляторе, который поддерживает аргументы по умолчанию C++0x для параметров шаблона функций, см.Включение шаблонов функций в C++0xдля деталей).

В предыдущем разделе была показана форма возврата<enable_if>. В качестве примера использования формы<enable_if>, которая работает через дополнительный параметр функции, функция<foo>в предыдущем разделе также может быть записана как:

template <class T>
T foo(T t,
    typename enable_if<boost::is_arithmetic<T> >::type* dummy = 0);

Следовательно, добавляется дополнительный параметр типа<void*>, но ему присваивается значение по умолчанию, чтобы сохранить параметр скрытым от кода клиента. Обратите внимание, что второй аргумент шаблона не был приведен<enable_if>, так как по умолчанию<void>дает желаемое поведение.

Какой способ написания активатора во многом дело вкуса, но для определенных функций возможно только подмножество вариантов:

  • Многие операторы имеют фиксированное число аргументов, поэтому<enable_if>необходимо использовать либо в типе возврата, либо в дополнительном шаблонном параметре.
  • Функции, которые имеют вариадный список параметров, должны использовать либо форму обратного типа, либо дополнительный параметр шаблона.
  • Конструкторы не имеют возвратного типа, поэтому вы должны использовать дополнительный параметр функции или дополнительный параметр шаблона.
  • Конструкторы, которые имеют вариадный список параметров, должны иметь дополнительный параметр шаблона.
  • Операторы преобразования могут быть написаны только с дополнительным параметром шаблона.

В компиляторе, который поддерживает аргументы по умолчанию C++0x для параметров шаблона функций, вы можете включить и отключить шаблоны функций, добавив дополнительный параметр шаблона. Этот подход работает во всех ситуациях, когда вы используете либо форму возвратного типа<enable_if>, либо форму параметра функции, включая операторов, конструкторов, шаблоны вариадных функций и даже перегруженные операции преобразования.

Как пример:

#include <boost/type_traits/is_arithmetic.hpp>
#include <boost/type_traits/is_pointer.hpp>
#include <boost/utility/enable_if.hpp>
class test
{
public:
    // A constructor that works for any argument list of size 10
    template< class... T,
        typename boost::enable_if_c< sizeof...( T ) == 10,
            int >::type = 0>
    test( T&&... );
    // A conversion operation that can convert to any arithmetic type
    template< class T,
        typename boost::enable_if< boost::is_arithmetic< T >,
            int >::type = 0>
    operator T() const;
    // A conversion operation that can convert to any pointer type
    template< class T,
        typename boost::enable_if< boost::is_pointer< T >,
            int >::type = 0>
    operator T() const;
};
int main()
{
    // Works
    test test_( 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 );
    // Fails as expected
    test fail_construction( 1, 2, 3, 4, 5 );
    // Works by calling the conversion operator enabled for arithmetic types
    int arithmetic_object = test_;
    // Works by calling the conversion operator enabled for pointer types
    int* pointer_object = test_;
    // Fails as expected
    struct {} fail_conversion = test_;
}

Специализации шаблонов классов могут быть включены или отключены<enable_if>. Для выражений активатора необходимо добавить один дополнительный параметр шаблона. Этот параметр имеет значение по умолчанию<void>. Например:

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> { ... };

Обоснование<A>с любым интегральным типом соответствует первой специализации, тогда как любой тип с плавающей точкой соответствует второй. Все остальные типы соответствуют основному шаблону. Условием может быть любое компиляционно-временное булево выражение, зависящее от шаблонных аргументов класса. Обратите внимание, что второй аргумент<enable_if>не нужен; по умолчанию<void>является правильным значением.

Шаблон<enable_if_has_type>может использоваться в этом сценарии, но вместо того, чтобы использовать черты типа для включения или отключения специализации, он использует контекст SFINAE для проверки существования зависимого типа внутри его параметра. Например, следующая структура извлекает зависимое<value_type>из T, если и только если<T::value_type>существует.

template <class T, class Enable = void>
class value_type_from
{
  typedef T type;
};
template <class T>
class value_type_from<T, typename enable_if_has_type<typename T::value_type>::type>
{
  typedef typename T::value_type type;
};

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

template <class T>
typename enable_if<boost::is_integral<T>, void>::type
foo(T t) {}
template <class T>
typename enable_if<boost::is_arithmetic<T>, void>::type
foo(T t) {}

Все интегральные типы также являются арифметическими. Поэтому, скажем, для вызова<foo(1)>оба условия верны, и обе функции, таким образом, находятся в наборе разрешения перегрузки. Оба матча одинаково хороши и, следовательно, неоднозначны. Конечно, одновременно может быть верным более одного условия, если другие аргументы разграничивают функции.

Вышеизложенное относится и к использованию<enable_if>в шаблоне классов частичных специализаций.

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

template <class T, class U> class mult_traits;
template <class T, class U>
typename enable_if<is_multipliable<T, U>,
    typename mult_traits<T, U>::type>::type
operator*(const T& t, const U& u) { ... }

Предположим, что шаблон класса<mult_traits>является классом признаков, определяющим результирующий тип оператора умножения. Класс<is_multipliable>признаков определяет, для каких типов включить оператора. Когда<is_multipliable<A,B>::value><true>для некоторых типов<A>и<B>, тогда<mult_traits<A,B>::type>определяется.

Теперь попытка вызвать (некоторую другую перегрузку)<operator*>, скажем, с типами операндов<C>и<D>, для которых<is_multipliable<C,D>::value>является<false>и<mult_traits<C,D>::type>не определена, является ошибкой на некоторых компиляторах. Принцип SFINAE не применяется, поскольку недействительный тип возникает как аргумент к другому шаблону.<lazy_enable_if>и<lazy_disable_if>шаблоны (и их<_c>версии) могут использоваться в таких ситуациях:

template<class T, class U>
typename lazy_enable_if<is_multipliable<T, U>,
    mult_traits<T, U> >::type
operator*(const T& t, const U& u) { ... }

Второй аргумент<lazy_enable_if>должен быть типом класса, который определяет вложенный тип, названный<type>, когда первый параметр (условие) является истинным.

[Note] Note

Ссылка на один тип члена или статическую константу в классе признаков приводит к тому, что все члены (тип и статическая константа) этой специализации инстанцируются. Поэтому, если ваши классы признаков иногда могут содержать недействительные типы, вы должны использовать два разных шаблона для описания условий и отображения типов. В приведенном выше примере<is_multipliable<T,U>::value>определяется, когда<mult_traits<T,U>::type>является действительным.

Некоторые компиляторы флажки функционируют как двусмысленные, если единственным отличительным фактором является другое условие в активаторе (хотя функции никогда не могут быть двусмысленными). Например, некоторые компиляторы (например, GCC 3.2) диагностируют следующие две функции как неоднозначные:

template <class T>
typename enable_if<boost::is_arithmetic<T>, T>::type
foo(T t);
template <class T>
typename disable_if<boost::is_arithmetic<T>, T>::type
foo(T t);

Могут применяться два обходных пути:

  • Используйте дополнительный фиктивный параметр, который разграничивает функции. Используйте значение по умолчанию, чтобы скрыть параметр от абонента. Например:

    <
    template<int>structdummy{dummy(int){}};
    template<classT>
    typenameenable_if<boost::is_arithmetic<T>,T>::type
    foo(Tt,dummy<0>=0);
    template<classT>
    typenamedisable_if<boost::is_arithmetic<T>,T>::type
    foo(Tt,dummy<1>=0);
    
    >
  • Определите функции в разных пространствах имен и введите их в общее пространство имен с<using>декларациями:

    <
    namespaceA{
       template<classT>
       typenameenable_if<boost::is_arithmetic<T>,T>::type
       foo(Tt);
    }
    namespaceB{
       template<classT>
       typenamedisable_if<boost::is_arithmetic<T>,T>::type
       foo(Tt);
    }
    usingA::foo;
    usingB::foo;
    
    >

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

Мы благодарны Говарду Хиннанту, Джейсону Ширку, Полу Менсонидесу и Ричарду Смиту, чьи открытия повлияли на библиотеку.

  • <enable_if>Jaakko Järvi, Jeremiah Willcock, Howard Hinnant и Andrew Lumsdaine. Функция перегрузки основана на произвольных свойствах типов.C++ Users Journal, 21(6):25--32, июнь 2003.
  • <enable_if>Jaakko Järvi, Jeremiah Willcock и Andrew Lumsdaine. Концептуально контролируемый полиморфизм. Фрэнк Пфенниг и Яннис Смарагдакис, редакторыГенеративное программирование и проектирование компонентов, том 2830LNCS, страницы 228-244. Springer Verlag, сентябрь 2003.
  • Дэвид Вандеворд и Николай М. Джосуттис.C++ Templates: The Complete Guide. Addison-Wesley, 2002.

Статья enable_if раздела Chapter 1. Boost.Core Chapter 1. Boost.Core может быть полезна для разработчиков на c++ и boost.




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



:: Главная :: Chapter 1. Boost.Core ::


реклама


©KANSoftWare (разработка программного обеспечения, создание программ, создание интерактивных сайтов), 2007
Top.Mail.Ru

Время компиляции файла: 2024-08-30 11:47:00
2025-05-19 17:50:49/0.033905982971191/1