Значения по умолчанию в шаблонах с аргументами шаблона (С++)
Предположим, у меня есть шаблон (называемый ExampleTemplate), который принимает два аргумента: тип контейнера (например, список, вектор) и содержащийся тип (например, float, bool и т.д.). Поскольку контейнеры на самом деле являются шаблонами, этот шаблон имеет параметр шаблона. Вот что я должен был написать:
#include <vector>
#include <list>
using namespace std;
template < template <class,class> class C, typename T>
class ExampleTemplate {
C<T,allocator<T> > items;
public:
....
};
main()
{
ExampleTemplate<list,int> a;
ExampleTemplate<vector,float> b;
}
Вы можете спросить, что такое "распределитель". Ну, вначале я пробовал очевидную вещь...
template < template <class> class C, typename T>
class ExampleTemplate {
C<T> items;
};
... но я, к сожалению, обнаружил, что аргумент по умолчанию для распределителя...
vector<T, Alloc>
list<T, Alloc>
etc
... должен был быть явно "зарезервирован" в объявлении шаблона.
Это, как вы видите, делает код более уродливым и заставляет меня воспроизводить значения по умолчанию аргументов шаблона (в данном случае - распределителя).
Что такое BAD.
РЕДАКТИРОВАТЬ: Вопрос не в конкретной проблеме с контейнерами - речь идет о "значениях по умолчанию в шаблонах с аргументами шаблона", и приведенное выше является всего лишь примером. Ответы, зависящие от знания того, что контейнеры STL имеют значение ":: value_type", не соответствуют мне. Подумайте об общей проблеме: если мне нужно использовать аргумент шаблона C в шаблоне ExampleTemplate, то в теле ExampleTemplate мне нужно воспроизвести аргументы по умолчанию C, когда я его использую? Если мне это нужно, не вводит ненужные повторения и другие проблемы (в этом случае, где C является контейнером STL, проблемы с переносимостью - например, "распределитель" )?
Ответы
Ответ 1
Возможно, вы предпочтете это:
#include <vector>
#include <list>
using namespace std;
template <class Container>
class ForExamplePurposes {
typedef typename Container::value_type T;
Container items;
public:
};
int main()
{
ForExamplePurposes< list<int> > a;
ForExamplePurposes< vector<float> > b;
}
В этом случае используется статический duck typing". Это также немного более гибко, поскольку оно не заставляет тип контейнера поддерживать концепцию STL Allocator.
Возможно, использование титров типа идиома может дать вам выход:
#include <vector>
#include <list>
using namespace std;
struct MyFunkyContainer
{
typedef int funky_type;
// ... rest of custom container declaration
};
// General case assumes STL-compatible container
template <class Container>
struct ValueTypeOf
{
typedef typename Container::value_type type;
};
// Specialization for MyFunkyContainer
template <>
struct ValueTypeOf<MyFunkyContainer>
{
typedef MyFunkyContainer::funky_type type;
};
template <class Container>
class ForExamplePurposes {
typedef typename ValueTypeOf<Container>::type T;
Container items;
public:
};
int main()
{
ForExamplePurposes< list<int> > a;
ForExamplePurposes< vector<float> > b;
ForExamplePurposes< MyFunkyContainer > c;
}
Кто-то, кто хочет использовать ForExamplePurposes
с контейнером, не совместимым с STL, должен будет специфицировать класс признаков ValueTypeOf
.
Ответ 2
Я бы предложил создать адаптеры.
Ваш класс должен быть создан с точным уровнем персонализации, который требуется классу:
template <template <class> C, template T>
class Example
{
typedef T Type;
typedef C<T> Container;
};
EDIT: попытка предоставить больше приятна, но обречена на провал, посмотрите на различные расширения:
-
std::vector<T>
: std::vector<T, std::allocator<T>>
-
std::stack<T>
: std::stack<T, std::deque<T>>
-
std::set<T>
: std::set<T, std::less<T>, std::allocator<T>>
Второй - это адаптер, поэтому он не принимает распределитель, а третий не имеет такой же арности. Поэтому вам нужно поставить бремя на пользователя.
Если пользователь хочет использовать его с типом, который не учитывает выраженную ясность, то самым простым способом для него является предоставление (локально) адаптера:
template <typename T>
using Vector = std::vector<T>; // C++0x
Example<Vector, bool> example;
Мне интересно об использовании пакетов параметров (variadic templates) здесь... Я не знаю, объявит ли C
как template <class...> C
трюк, или если компилятор потребует переменный класс, то.
Ответ 3
Вы должны указать полную подпись шаблона, включая параметры по умолчанию, если вы хотите использовать шаблон шаблона обычным способом.
template <typename T, template <class U, class V = allocator<U> > class C>
class ExampleTemplate {
C<T> items;
public:
....
};
Если вы хотите обрабатывать другие контейнеры, которые принадлежат STL, вы можете делегировать конструкцию контейнера помощнику.
// Other specialization failed. Instantiate a std::vector.
template <typename T, typename C>
struct make_container_
{
typedef std::vector<T> result;
};
// STL containers
template <typename T, template <class U, class V = allocator<U> > class C>
struct make_container_<T,C>
{
typedef C<T> result;
};
// Other specializations
...
template <typename T, typename C>
class ExampleTemplate {
make_container_<T,C>::result items;
public:
....
};
Ответ 4
Я думаю, требуется воспроизвести все параметры шаблона, даже по умолчанию. Обратите внимание, что сам стандарт не использует параметры шаблона шаблона для адаптеров-утилизаторов и предпочитает использовать обычные параметры шаблона:
template < class T , class Container = deque <T > > class queue { ... };
template < class T , class Container = vector <T>, class Compare = less < typename Container :: value_type > > class priority_queue { ... };
Ответ 5
Следующий код позволит вам сделать что-то подобное, о чем вы просите. Конечно, это не будет работать со стандартными контейнерами, так как это уже должно быть частью класса шаблона, который передается в шаблон.
/* Allows you to create template classes that allow users to specify only some
* of the default parameters, and some not.
*
* Example:
* template <typename A = use_default, typename B = use_default>
* class foo
* {
* typedef use_default_param<A, int> a_type;
* typedef use_default_param<B, double> b_type;
* ...
* };
*
* foo<use_default, bool> x;
* foo<char, use_default> y;
*/
struct use_default;
template<class param, class default_type>
struct default_param
{
typedef param type;
};
template<class default_type>
struct default_param<use_default, default_type>
{
typedef default_type type;
};
Но я действительно не думаю, что это то, что вы ищете. То, что вы делаете с контейнерами, вряд ли применимо к произвольным контейнерам, так как многие из них будут иметь проблему с несколькими параметрами по умолчанию с неочевидными типами по умолчанию.
Ответ 6
Как точно вопрос о проблеме, которую я имел в своем коде (- Я использую Visual Studio 2015), я понял альтернативное решение, которое я хотел бы разделить.
Идея такова: вместо того, чтобы передавать шаблон шаблона шаблону класса ExampleTemplate
, можно также передать нормальное имя типа, которое содержит тип DummyType
как фиктивный параметр, например std::vector<DummyType>
.
Затем внутри класса замените этот параметр фиктивного кода на что-то разумное. Для замены типа могут использоваться следующие вспомогательные классы:
// this is simply the replacement for a normal type:
// it takes a type T, and possibly replaces it with ReplaceByType
template<typename T, typename ReplaceWhatType, typename ReplaceByType>
struct replace_type
{
using type = std::conditional_t<std::is_same<T, ReplaceWhatType>::value, ReplaceByType, T>;
};
// this sets up the recursion, such that replacement also happens
// in contained nested types
// example: in "std::vector<T, allocator<T> >", both T are replaced
template<template<typename ...> class C, typename ... Args, typename ReplaceWhatType, typename ReplaceByType>
struct replace_type<C<Args ...>, ReplaceWhatType, ReplaceByType>
{
using type = C<typename replace_type<Args, ReplaceWhatType, ReplaceByType>::type ...>;
};
// an alias for convenience
template<typename ... Args>
using replace_type_t = typename replace_type<Args ...>::type;
Обратите внимание на рекурсивный шаг в replace_type
, который заботится о том, чтобы типы, вложенные в другие классы, также заменялись - с этим, например, в std::vector<T, allocator<T> >
, заменяются как T
, так и не только первый, То же самое относится к более чем одной иерархии вложенности.
Затем вы можете использовать это в своем ExampleTemplate
-class,
struct DummyType {};
template <typename C, typename T>
struct ExampleTemplate
{
replace_type_t<C, DummyType, T> items;
};
и назовите его через
int main()
{
ExampleTemplate<std::vector<DummyType>, float> a;
a.items.push_back(1.0);
//a.items.push_back("Hello"); // prints an error message which shows that DummyType is replaced correctly
ExampleTemplate<std::list<DummyType>, float> b;
b.items.push_back(1.0);
//b.items.push_back("Hello"); // prints an error message which shows that DummyType is replaced correctly
ExampleTemplate<std::map<int, DummyType>, float> c;
c.items[0]=1.0;
//c.items[0]="Hello"; // prints an error message which shows that DummyType is replaced correctly
}
DEMO
Помимо синтаксиса not-that-nice, это имеет то преимущество, что
-
Он работает с любым количеством параметров шаблона по умолчанию - например, рассмотрим случай с std::map
в этом примере.
-
Нет необходимости явно указывать любые параметры шаблона по умолчанию.
-
Он может быть легко расширен до более фиктивных параметров (тогда как это, вероятно, не должно вызывать пользователи...).
Кстати: вместо фиктивного типа вы также можете использовать std::placeholder
... только что поняли, что это может быть немного лучше.