Вывод аргумента шаблона - PullRequest
5 голосов
/ 18 января 2012

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

Я начну с кода в заголовке:

template<typename T, template<class = T> class A,
         template<class = T, template<class=T> class = A> class C>
class queryable
{
public:
    typedef T value_type;
    typedef A<value_type> allocator_type;
    typedef C<value_type, allocator_type> container_type;    // (1)
    typedef queryable<T, A, C> type;
    queryable(container_type const &) { }
    template<typename _Out> queryable<_Out, A, C> select(/* some delegate */);
    // more methods etc
}

И вот как бы я хотел, чтобы это было создано:

std::vector<int> my_vec;
queryable<std::vector<int> > q(my_vec);

Само собой разумеется, что это не работает (иначе я не был бы здесь :))

Теперь еще более странно то, что даже это, похоже, не работает:

std::vector<int> my_vec;
queryable<int, std::allocator, std::vector> q(my_vec);

Как вы можете видеть (глядя на функцию выбора), для меня важно не просто использовать что-то вроде этого:

template<typename T> class queryable;

Есть предложения, как это решить? И возможно ли это вообще?

Любая помощь будет оценена!

РЕДАКТИРОВАТЬ: ошибки, которые я получаю:

../entry.cpp:19:58: error: type/value mismatch at argument 3 in template parameter list for ‘template<class T, template<class> class A, template<class, template<class> class<template-parameter-2-2> > class C> class failproof::collections::queryable’
../entry.cpp:19:58: error:   expected a template of type ‘template<class, template<class> class<template-parameter-2-2> > class C’, got ‘template<class _Tp, class _Alloc> class std::vector’
../entry.cpp:19:61: error: invalid type in declaration before ‘;’ token

РЕДАКТИРОВАТЬ 2:

Насколько я понимаю, компилятор жалуется, что C не принимает 2 аргумента класса, а 1 аргумент класса и 1 аргумент шаблона (1), потому что я определил C таким образом. Есть ли способ решить эту проблему?

Ответы [ 3 ]

7 голосов
/ 19 января 2012

Существует общий метод для "разнесения" типа, чтобы проверить, был ли он создан шаблоном, и для извлечения типов, которые были переданы этому шаблону.Также вы можете получить доступ к самому шаблону и передать ему другие параметры, если хотите.

vector - это шаблон класса .Когда вы применяете к нему параметры, вы получаете что-то вроде vector<int>, то есть шаблон класса . шаблонный класс - это особый тип, как и любой другой тип, просто он создан с помощью шаблона класса.

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

(Технически, vector - это шаблон с двумя аргументами.значение по умолчанию для второго параметра, поэтому vector<int> на самом деле vector<int, allocator<int> >, но это все же в основном шаблон с двумя аргументами, а не шаблон с одним аргументом.)

Лучшее место для начала - это пример кода, который я поставил на ideone .Я скопирую код Exploder в конце этого ответа.

Я начинаю с

typedef list<int> list_of_ints;

и продолжаю использовать шаблон Exploder для доступа ко всей вышеуказанной информации.Например, Exploder<list_of_ints> :: type_1 - это первый параметр, который был передан в шаблон, в данном случае int.Второй параметр (это параметр по умолчанию) равен allocator<int> и доступен с Exploder<list_of_ints> :: type_2.

typedef Exploder<list_of_ints> :: type_2  should_be_an_allocator_int;

Учитывая этот второй тип, который, как мы знаем, был создан шаблоном, мы можем получить доступ к его параметрувведите int с Exploder< should_be_an_allocator_int > :: type_1, но более интересно получить доступ к шаблону allocator и передать ему другой параметр.Следующая строка оценивает, в этом примере, allocator<double>.

typedef Exploder< should_be_an_allocator_int >
           :: rebind<double> :: type should_be_an_allocator_double;

Таким образом, даже если ваш тип list<...,...> не не использовал распределитель по умолчанию, вы можете получить доступ киспользованный распределитель, а также любой шаблон класса , использованный для создания типа распределителя.

Теперь, когда у нас есть подходящий распределитель, мы можем вернуться к нашему исходному шаблону класс list<int> и замените int на double:

Exploder<list_of_ints> :: rebind<double, should_be_an_allocator_double> :: type

Чтобы убедиться, что все это работает, в примере кода используется typeid(...).name() для печати фактического типа различных объектов,вместе с правильным типом, который должен быть.Вы можете видеть, что они совпадают.

(Кроме того, некоторые шаблоны таковы, что их параметры являются не типами, а другими шаблонами классов или даже другими шаблонами шаблонов .Должна быть возможность извлечь все это, но я не буду вдаваться в подробности.)

(Последнее интересное техническое примечание. Некоторые типы, такие как allocator, имеют нечто, называемое rebindчтобы разрешить такого рода доступ. Но техника, использованная выше, работает для всех шаблонных классов, даже для тех, у кого нет rebind)

Полный код для шаблона Exploder

См. Пример кода, который я поставил на ideone для полной демонстрации.

template <class>
struct Exploder;

template<class T, template<class> class Template>
struct Exploder< Template<T> > {
        static const char * description() { return " One-arg template. Arg 1 is a type "; }
        typedef T type_1;
        template <class V>
        struct rebind {
                typedef Template<V> type;
        };
};
template<class T, class U, template<class,class> class Template>
struct Exploder< Template<T,U> > {
        static const char * description() { return " Two-arg template. All args are types, as opposed to being (unapplied) templates. "; }
        typedef T type_1;
        typedef U type_2;
        template <class V,class W>
        struct rebind {
                typedef Template<V,W> type;
        };
};
template<class S, class T, class U, template<class,class,class> class Template>
struct Exploder< Template<S,T,U> > {
        static const char * description() { return " Three-arg template. All args are types, as opposed to being (unapplied) templates. "; }
        typedef S type_1;
        typedef T type_2;
        typedef U type_3;
};
6 голосов
/ 18 января 2012

Второй параметр шаблона стандартных контейнеров (распределитель) - это тип , а не шаблон , поэтому вам нужно изменить третий параметр на что-то вроде

template<typename, typename> class C

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

Тогда вы сможете создать экземпляр шаблона как

queryable<int, std::allocator, std::vector>

Возможно, вам будет лучше просто параметризовать тип контейнера, а затем использовать его определения value_type и allocator_type:

template <typename C> class queryable
{
public:
    typedef typename C::value_type value_type;
    typedef typename C::allocator_type allocator_type;
    typedef C container_type;
};

(Одним из недостатков является то, что вы не можете получить прямой доступраспределитель шаблон , однако вы можете использовать определение распределителя type rebind, если вам нужно создать экземпляр этого шаблона для других типов.)

Такжеtypedef iterator const const_iterator; неверно;который объявляет неизменяемый итератор, который может использоваться для изменения последовательности, в то время как const_iterator должен быть модифицируемым итератором, который не может используется для изменения последовательности.

1 голос
/ 18 января 2012

(Примечание по терминологии. vector - это шаблон класса , то есть без каких-либо параметров. И vector<int> - это шаблон класса , то есть класс почти такой же, как любой за исключением того, что он был создан с помощью шаблона.)

Можно использовать его по своему усмотрению, где запрашиваемый принимает один параметр шаблона:

queryable< vector<int> > q;

Это означает, что querable - это шаблон с одним параметром:

template <typename T>
struct queryable;

Затем вы используете специализацию , которая имеет более одного параметра:

template <typename ValueType, template<class T,class = allocator<T> > class ContainerTemplate>
struct queryable< ContainerTemplate<Contained> > {
        typedef ValueType value_type;
        typedef ContainerTemplate<ValueType> container_type;
        typedef typename container_type :: allocator_type A;
        // typedef ContainerTemplate <WhateverOtherValueTypeYouWish> ...
        // typedef A :: rebind <SomeOtherType> ...
};

Последние две строки, закомментированные, показывают, как вы можете использовать ContainerTemplate в качестве шаблона класса , создавая другие типы по мере необходимости. ContainerTemplate это vector или list или set или что-то в этом роде.

Как отметил @MikeSeymour, использование rebind может быть способом доступа к распределителю шаблон класса .

Пример кода на ideone

...