Учитывая, что на входе находится контейнер определенного типа, можно ли «извлечь» шаблон контейнера и создать его экземпляр для другого типа? Например:
void f ( auto& x ) {
getContainerTemplate(x)<int> a (10); // getContainerTemplate()
// is some magic function.
}
Эффект f()
должен быть следующим в зависимости от типа x
:
void f ( std::vector<double>& x ) {
std::vector<int> a(10);
}
или
void f ( parlay::sequence<double>& x ) { // parlay::sequence is
// another container template storing elements contiguously.
parlay::sequence<int> a(10);
}
Если вы готовы удалить какие-либо дополнительные параметры шаблона помимо первого (если таковые имеются), разрешив им получать значения по умолчанию, то что-то вроде этого подойдет.
Опубликуйте это как ответ @IgorTandetnik. Я пытался найти похожее решение, но оно было не таким чистым.
@Игорь Тандетник - вы можете использовать allocator_traits для повторной привязки нового распределителя
@Gene Да, но вам нужно будет жестко запрограммировать тот факт, что второй параметр на самом деле является распределителем, тем самым теряя общность.
Что-то в этом роде, возможно:
template <typename T, typename C>
struct ContainerTypeReplacer;
template <typename T, template <typename...> class C, typename... Args>
struct ContainerTypeReplacer<T, C<Args...>> {
using type = C<T>;
};
template <typename T, typename C>
typename ContainerTypeReplacer<T, C>::type getContainerTemplate(const C&);
Использование:
void f (auto& x) {
// if x is of type e.g. std::vector<double>,
// then a is std::vector<int>
decltype(getContainerTemplate<int>(x)) a(10);
}
Да, это возможно. Использование «параметра шаблона шаблона» (см. Как можно использовать параметры шаблона шаблона?) вместо auto
позволит f()
определить тип шаблона, экземпляр которого затем можно создать с помощью собственных параметров, например:
template< template<typename, typename...> class Container, typename T >
void f ( Container<T>& x ) {
Container<int> a(10);
...
}
Каков структурированный подход к освоению метапрограммирования шаблонов?
Я не могу на этот вопрос ответить, так как я самоучка. Все, что я могу сказать, это изучать код других людей и практиковаться.
Начиная с С++ 20, у вас есть концепции, которые позволяют вам работать с одной и той же функцией шаблона с разными типами. Это может помочь повысить безопасность работы с различными типами. Например, проверьте std::convertible_to<T>
, std::same_as<T>
и std::is_floating_point_v<T>
.
Какое это имеет отношение к вопросу?
@cigien можно ли «извлечь» шаблон контейнера и создать его экземпляр для другого типа? - Мой ответ содержит более безопасный вариант «извлечения» некоторых типов/типов из командной таблички.
Достичь совершенства сложнее, чем кто-либо здесь признает.
Простой параметр шаблона шаблона работает в 80% случаев, но не в остальных. Например, если вы хотите сделать это с картами, это не сработает, потому что ваш компаратор может не совпадать.
Возможно, вам стоит подумать о том, чтобы превратить ваши контейнеры в моноиды в смысле Haskell. Эти моноиды описывают, как выполнять над ними определенные операции. В C++ вы публикуете эти операции через классы признаков; мы можем попытаться заставить его работать, используя общие шаблоны, а затем улучшить его для конкретных подтипов.
Самый простой способ — написать fmap
пункт настройки. fmap
принимает моноид и функцию и создает новый моноид с содержимым моноида, отображаемым функцией.
В контейнере это делает примерно то, что вы хотите.
Поддерживать базовые контейнеры последовательностей легко; затем вы можете расширить его для поддержки ассоциативных контейнеров. Это может пойти гораздо дальше; вы можете поддерживать интеллектуальные указатели, дополнительные параметры, моноиды ввода-вывода (представляющие «вычисления, которые необходимо выполнить после ввода пользователем данных»), фьючерсы и т. д.
Небольшая проблема заключается в том, что
std::vector<double>
на самом деле являетсяstd::vector<double, std::allocator<double>>
. Достаточно легко превратитьstd::vector<double, std::allocator<double>>
вstd::vector<int, std::allocator<double>>
, но тогда это, конечно, не скомпилируется. Мне не сразу понятно, как вместо этого превратить его вstd::vector<int, std::allocator<int>>
, без жесткого кодирования конкретных знаний о деталяхstd::vector
.