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

Concepts in Depth

Boost , The Boost C++ Libraries BoostBook Documentation Subset , Chapter 37. Boost.TypeErasure

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

(Источник примеров в этом разделе см.custom.cpp)

Ранее мы использовали<BOOST_TYPE_ERASURE_MEMBER>для определения концепции контейнеров, которые поддерживают<push_back>. Однако иногда этот интерфейс недостаточно гибкий. Библиотека также предоставляет интерфейс более низкого уровня, который дает полный контроль над поведением. Давайте посмотрим, что нам нужно, чтобы определить<has_push_back.>Во-первых, необходимо определить сам шаблон<has_push_back>. Приведем два шаблонных параметра: один для контейнера и один для типа элемента. Этот шаблон должен иметь статическую функцию члена, называемую прикладной, которая используется для выполнения операции.

template<class C, class T>
struct has_push_back
{
    static void apply(C& cont, const T& arg) { cont.push_back(arg); }
};

Теперь мы можем использовать это в<any>, используя<call>для отправки операции.

std::vector<int> vec;
any<has_push_back<_self, int>, _self&> c(vec);
int i = 10;
call(has_push_back<_self, int>(), c, i);
// vec is [10].

Наша вторая задача - настроить<any>так, чтобы мы могли позвонить<c.push_back(10)>. Мы специализируемся на этом<concept_interface>. Первый аргумент<has_push_back>, поскольку мы хотим ввести члена в каждый<any>, который использует концепцию<has_push_back>. Второй аргумент,<Base>, используется библиотекой для связывания нескольких применений<concept_interface>вместе. Мы должны унаследовать его публично.<Base>также используется для получения доступа к полному<any>типу. Третий аргумент — это заполнитель, который представляет это любое. Если кто-то использовал<push_back<_c,_b>>, мы хотим вставить в контейнер только элемент<push_back>, а не тип значения. Таким образом, третий аргумент — контейнерный заполнитель.

При определении<push_back>тип аргумента использует метафункцию<as_param>. Это только в том случае, если<T>является заполнителем. Если<T>не является заполнителем, то метафункция просто возвращает свой аргумент,<constT&>, без изменений.

namespace boost {
namespace type_erasure {
template<class C, class T, class Base>
struct concept_interface<has_push_back<C, T>, Base, C> : Base
{
    void push_back(typename as_param<Base, const T&>::type arg)
    { call(has_push_back<C, T>(), *this, arg); }
};
}
}

Теперь наш пример становится

std::vector<int> vec;
any<has_push_back<_self, int>, _self&> c(vec);
c.push_back(10);

Это то, чего мы хотим.

(Источник примеров в этом разделе см.overload.cpp)

<concept_interface>позволяет нам вводить произвольные заявления в<any>. Это очень гибко, но есть некоторые подводные камни, на которые стоит обратить внимание. Иногда мы хотим использовать одну и ту же концепцию несколько раз с разными параметрами. Специализироваться<concept_interface>таким образом, чтобы правильно справляться с перегрузками, немного сложно. Учитывая концепцию, мы хотели бы, чтобы следующее работало:

any<
    mpl::vector<
        foo<_self, int>,
        foo<_self, double>,
        copy_constructible<>
    >
> x = ...;
x.foo(1);   // calls foo(int)
x.foo(1.0); // calls foo(double)

Поскольку<concept_interface>создает линейную цепочку наследования, без какой-либо дополнительной работы одна перегрузка фоо скроет другую.

Вот методы, которые я нашел надежной работы.

Для членских функций я не смог найти способ избежать использования двух специализаций.

template<class T, class U>
struct foo
{
    static void apply(T& t, const U& u) { t.foo(u); }
};
namespace boost {
namespace type_erasure {
template<class T, class U, class Base, class Enable>
struct concept_interface< ::foo<T, U>, Base, T, Enable> : Base
{
    typedef void _fun_defined;
    void foo(typename as_param<Base, const U&>::type arg)
    {
        call(::foo<T, U>(), *this, arg);
    }
};
template<class T, class U, class Base>
struct concept_interface< ::foo<T, U>, Base, T, typename Base::_fun_defined> : Base
{
    using Base::foo;
    void foo(typename as_param<Base, const U&>::type arg)
    {
        call(::foo<T, U>(), *this, arg);
    }
};
}
}

Это использует SFINAE для определения необходимости использования декларации. Обратите внимание, что четвертый аргумент<concept_interface>является фиктивным параметром, который всегда является недействительным и предназначен для использования для SFINAE. Еще одно решение проблемы, которое я использовал в прошлом, заключается в том, чтобы ввести фиктивную декларацию<fun>и всегда помещать в декларацию использования. Это неполноценное решение по нескольким причинам. Требуется дополнительный интерфейс, чтобы добавить фиктивную перегрузку. Это также означает, что<fun>всегда перегружен, даже если пользователь попросил только одну перегрузку. Это затрудняет получение адреса веселья.

Обратите внимание, что при использовании SFINAE требуется дублирование некоторого кода, количество кода, которое должно быть дублировано, относительно невелико, поскольку реализация<concept_interface>обычно является одним вкладышем. Это немного раздражает, но я считаю, что это приемлемая цена вместо лучшего решения.

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

template<class T, class U>
struct bar_concept
{
    static void apply(T& t, const U& u) { bar(t, u); }
};
namespace boost {
namespace type_erasure {
template<class T, class U, class Base>
struct concept_interface< ::bar_concept<T, U>, Base, T> : Base
{
    friend void bar(typename derived<Base>::type& t, typename as_param<Base, const U&>::type u)
    {
        call(::bar_concept<T, U>(), t, u);
    }
};
template<class T, class U, class Base>
struct concept_interface< ::bar_concept<T, U>, Base, U, typename boost::disable_if<is_placeholder<T> >::type> : Base
{
    using Base::bar;
    friend void bar(T& t, const typename derived<Base>::type& u)
    {
        call(::bar_concept<T, U>(), t, u);
    }
};
}
}

По сути, мы должны специализироваться<concept_interface>один раз для каждого аргумента, чтобы убедиться, что перегрузка вводится в первый аргумент. Как вы могли заметить, типы аргументов немного сложны. В первой специализации первый аргумент использует<derived>вместо<as_param>. Причина этого в том, что если бы мы использовали<as_param>, то могли бы в конечном итоге нарушить правило одного определения, определив одну и ту же функцию дважды. Аналогично, мы используем SFINAE во второй специализации, чтобы убедиться, что бар определяется только один раз, когда оба аргумента являются заполнителями. Можно объединить две специализации с небольшим количеством метапрограммирования, но если у вас нет много аргументов, это, вероятно, не стоит того.

(Источник примеров в этом разделе см.concept_map.cpp)

Иногда полезно ненавязчиво адаптировать тип к моделированию концепции. Например, предположим, что мы хотим сделать<std::type_info>модель<less_than_comparable>. Для этого мы просто специализируемся на определении понятия.

namespace boost {
namespace type_erasure {
template<>
struct less_than_comparable<std::type_info>
{
    static bool apply(const std::type_info& lhs, const std::type_info& rhs)
    { return lhs.before(rhs) != 0; }
};
}
}

[Note]Note

Большинство, но не все встроенные концепции могут быть специализированными. Конструкторы, деструкторы и РТТИ нуждаются в особой обработке из библиотеки и не могут быть специализированными. Специализироваться могут только примитивные понятия, так что итераторы тоже выходят.

(Источник примеров в этом разделе см.associated.cpp)

Связанные типы, такие как<typename T::value_type>или<typename std::iterator_traits<T>::reference>, довольно распространены в программировании шаблонов. Повышаю. Для этого они используют<deduced>.<deduced>подобен обычному<placeholder>, за исключением того, что тип, с которым он связывается, определяется вызовом метафункции и не нуждается в явном указании.

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

template<class T>
struct pointee
{
    typedef typename mpl::eval_if<is_placeholder<T>,
        mpl::identity<void>,
        boost::pointee<T>
    >::type type;
};

Обратите внимание, что мы не можем просто использовать<boost::pointee>, потому что эта метафункция должна быть безопасной. Неважно, что он возвращается, если он не дает ошибки. (Библиотека никогда не пытается сделать это с помощью заполнителя, но поиск, зависящий от аргументов, может вызвать ложные предположения.)

template<class T = _self>
struct pointer :
    mpl::vector<
        copy_constructible<T>,
        dereferenceable<deduced<pointee<T> >&, T>
    >
{
    // provide a typedef for convenience
    typedef deduced<pointee<T> > element_type;
};

Теперь в концепции<x>используются два заполнителя<_self>и<pointer<>::element_type>. Когда мы строим<x>с<int*>,<pointer<>::element_type>получается<pointee<int*>::type>, что<int>. Таким образом, отказ от ссылки<x>возвращает<any>, который содержит<int>.

int i = 10;
any<
    mpl::vector<
        pointer<>,
        typeid_<pointer<>::element_type>
    >
> x(&i);
int j = any_cast<int>(*x); // j == i

Иногда мы хотим, чтобы ассоциированный тип был конкретным типом. Это можно решить с помощью концепции<same_type>. Здесь мы создаем любой, который может содержать любой указатель, тип элемента которого<int>.

int i = 10;
any<
    mpl::vector<
        pointer<>,
        same_type<pointer<>::element_type, int>
    >
> x(&i);
std::cout << *x << std::endl; // prints 10

Использование<same_type>фактически приводит к тому, что библиотека заменяет все виды использования<pointer<>::element_type>на<int>и подтверждает, что она всегда связана с<int>.<x>,<int>, [скрыто], [скрыто].

<same_type>также может использоваться для двух заполнителей. Это позволяет нам использовать простое имя, а не записывать связанный тип снова и снова.

int i = 10;
any<
    mpl::vector<
        pointer<>,
        same_type<pointer<>::element_type, _a>,
        typeid_<_a>,
        copy_constructible<_a>,
        addable<_a>,
        ostreamable<std::ostream, _a>
    >
> x(&i);
std::cout << (*x + *x) << std::endl; // prints 20


PrevUpHomeNext

Статья Concepts in Depth раздела The Boost C++ Libraries BoostBook Documentation Subset Chapter 37. Boost.TypeErasure может быть полезна для разработчиков на c++ и boost.




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



:: Главная :: Chapter 37. Boost.TypeErasure ::


реклама


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

Время компиляции файла: 2024-08-30 11:47:00
2025-05-19 16:58:15/0.031818151473999/1