C++11 "позднее связывание" аргументов шаблона
Пожалуйста, не поймите мою "позднюю привязку" неправильно, я не имею в виду обычную позднюю привязку во время выполнения, я имею в виду что-то другое и не могу найти лучшего слова для этого:
Предположим, я работаю над контейнерной (или аналогичной) структурой данных Containor
для некоторого типа значения V
что нужно сравнить эти значения с компаратором, поэтому мой первый шаблон выглядит следующим образом
template<typename Val, typename Comp = std::less<Val>>
struct Containor{};
Теперь мой Containor
структура использует другой контейнер внутри. Какой контейнер должен использоваться, также должен быть конфигурируем с помощью аргументов шаблона, скажем, по умолчанию std::set
, Так что моя следующая версия Containor
выглядит так:
template<typename Val, typename Comp = std::less<Val>, typename Cont = std::set<Val,Comp>>
struct Containor{};
и вот тут код начинает пахнуть ИМХО. Пока пользователь удовлетворен реализацией внутреннего контейнера по умолчанию, все в порядке. Однако предположим, что он хочет использовать новую реализацию Google btree set btree::btree_set
вместо std::set
, Затем он должен создать экземпляр шаблона следующим образом:
typedef Containor<int,std::less<int>,btree::btree_set<int,std::less<int>> MyContainor;
^^^^^^^^^^^^^^^^^^^
Я подчеркнул ту часть, в которой лежит моя проблема. КОД КЛИЕНТА должен создать экземпляр btree_set с правильными параметрами. Это честно отстой, потому что Containor
Классу всегда нужен набор точно такого же типа и компаратора, как и его первые два аргумента шаблона. Клиент может - случайно - вставить сюда другие типы! Кроме того, на клиенте лежит бремя выбора правильных параметров. Это может быть легко в этом случае, но трудно, если внутренний контейнер должен, например, представлять собой набор пар типа значения и некоторого другого типа. Тогда клиенту еще труднее получить правильные параметры типа внутреннего набора.
Так что я хочу, чтобы клиентский код передавал только необработанный шаблон и Containor
внутренне инстанцирует это с правильными аргументами, то есть что-то вроде этого:
template<typename Val, typename Comp = std::less<Val>, typename Cont = std::set >
struct Containor{
typedef Cont<Val,Comp> innerSet;
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ container instanciates the inner containor
};
typedef Containor<int,std::less<int>,btree::btree_set> MyContainor;
// ^^^^^^^^^^^^^^^^
// client only hands in raw template
Конечно, это не действительный C++!
Вот я и подумал о способах решения этой проблемы. Единственное решение, о котором я мог подумать, - это написание "связующих классов" для всех структур данных, которые я хочу использовать, например:
struct btree_set_binder{
template<typename V, typename C = std::less<V>>
struct bind{
typedef btree::btree_set<V,C> type;
}
};
Теперь я могу определить мой Containor
с набором связующего
template<typename Val, typename Comp = std::less<Val>, typename ContBinder = btree_set_binder >
struct Containor{
typedef btree_set_binder::bind<Val,Comp>::type innerSet;
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ works like a charm
};
Теперь пользователь должен предоставить только нужный класс связующего и Containor
будет подтверждать это с правильными аргументами. Так что эти классы связывателей были бы хороши для меня, но довольно сложно писать классы связывателей для всех контейнеров. Так есть ли лучший или более простой способ привязать аргументы шаблона "поздно" в C++11, то есть внутри другого шаблона, который извлекает необработанный шаблон в качестве параметра.
3 ответа
Может быть, сделать свою собственную черту компаратора.
// Comparator trait primary template
template <typename T> stuct MyComparator
{
typedef typename T::key_compare type;
};
// Comparator trait non-standard usage example
template <typename U, typename V, int N>
struct MyComparator<WeirdContainer<U, V, N>>
{
typedef std::greater<V> type;
};
template <typename T, typename Cont = std::set<T>>
struct MyAdaptor
{
typedef typename MyComparator<Cont>::type comparator_type;
typedef T value_type;
// ...
};
Я переименовал ваш "Контейнер" в "MyAdaptor", так как этот вид конструкции обычно называется классом "адаптера".
Использование:
MyAdaptor<int> a; // uses std::set<int> and std::less<int>
MyAdaptor<double, WeirdContainer<bool, double, 27>> b;
Обновление: в свете обсуждения вы можете даже полностью удалить аргумент внешнего типа:
template <typename Cont> struct MyBetterAdaptor
{
typedef MyAdaptorTraits<Cont>::value_type value_type;
typedef MyAdaptorTraits<Cont>::pred_type pred_type;
// ...
};
Быть использованным так:
MyBetterAdaptor<std::set<int>> c; // value type "int", predicate "std::less<int>"
Написание MyAdaptorTraits
Шаблон оставлен в качестве упражнения.
Поэтому я хочу, чтобы клиентский код передавал только необработанный шаблон, а контейнер внутренне создавал его с правильными аргументами,
Так что, очевидно, вам нужен параметр шаблона шаблона:
// std::set has three template parameters,
// we only want to expose two of them ...
template <typename V, typename C>
using set_with_defalloc = std::set<V,C>;
template<
typename Val,
typename Comp = std::less<Val>,
template <typename V, typename C> class Cont = set_with_defalloc>
struct Containor{
typedef Cont<Val,Comp> innerSet;
// ...
};
Вы должны быть в состоянии сделать это с template
template
параметры, как в:
template<typename Val, typename Comp = std::less<Val>, template <typename...> class ContBinder = std::set>
struct Containor {
typedef ContBinder<Val, Comp> innerSet;
// ...
};
Примечание: вам нужен вариант typename...
так как std::set
принимает три параметра шаблона (третий является распределителем), а другие контейнеры - нет.