C++にはtemplate template parameterと呼ばれる魔術回路が備わっているが、活用している例が少ないのでメモ程度に実装と使用例を書いておく記事です。
例1 型が与えられたテンプレートから特殊化されているかを判断する
static_assert(is_specialized_from<std::vector<int>, std::vector>::value); // true
static_assert(!is_specialized_from<std::variant<int, std::string>, std::pair>::value); // false
実装
C++11の場合
template<class T, template<class...>class U>
struct is_specialized_from {
template<class>
struct make_void { using type = void; };
template<class, class enabler = void>
struct is_specialized_from_impl {
static constexpr bool value = false;
};
template<template<class...>class T_Base, class... T_Args>
struct is_specialized_from_impl<T_Base<T_Args...>, typename make_void<U<T_Args...>>::type> {
static constexpr bool value = std::is_same<T_Base<T_Args...>, U<T_Args...>>::value;
};
static constexpr bool value = is_specialized_from_impl<T>::value;
};
C++20の場合
template<class T, template<class...>class U>
struct is_specialized_from : std::false_type{};
template<template<class...>class T_Base, class... T_Args, template<class...>class U>
requires std::same_as<T_Base<T_Args...>, U<T_Args...>>
struct is_specialized_from<T_Base<T_Args...>, U> : std::true_type{};
用例
関数をユニバーサル転送で書くことができる。
// ユニバーサル転送
template<class T>
requires is_specialized_from<std::remove_cvref_t<T>, std::vector>::value
void func(T&&);
// オーバーロードで解決しないといけない
template<class Value>
void func(std::vector<Value>&&);
template<typename Value>
void func(const std::vector<Value>&);
なお、std::arrayのような非型テンプレートが入っているものに対しては動かない。
例2 型特性テンプレートを型を引数にした関数オブジェクトのように扱う
std::tupleから与えられた型特性を満たす要素を取り出す
std::tuple<int, std::string, uint64_t>
からstd::integral
がtrueになる要素だけのtuple(std::tuple<int, uint64_t>
)を作りたい。
実装
C++14以降
template<class Tup, template<class>class Filter>
struct tuple_filter_impl{
template<class RemainIdxSeq, class ResultIdxSeq>
struct extract_matched_index{ using type = ResultIdxSeq; };
template<std::size_t IdxHead, std::size_t... IdxTails, std::size_t... ResultIdx>
struct extract_matched_index<std::index_sequence<IdxHead, IdxTails...>, std::index_sequence<ResultIdx...>>{
using type = std::conditional_t<Filter<std::tuple_element_t<IdxHead, Tup>>::value,
typename extract_matched_index<std::index_sequence<IdxTails...>, std::index_sequence<ResultIdx..., IdxHead>>::type,
typename extract_matched_index<std::index_sequence<IdxTails...>, std::index_sequence<ResultIdx...>>::type
>;
};
using index_seq = typename extract_matched_index<std::make_index_sequence<std::tuple_size<Tup>::value>, std::index_sequence<>>::type;
template<std::size_t... Is>
static auto extract(const Tup& tup, std::index_sequence<Is...>) {
return std::tuple<std::tuple_element_t<Is, Tup>...>{ std::get<Is>(tup)... };
}
template<std::size_t... Is>
static auto extract(Tup&& tup, std::index_sequence<Is...>) {
return std::tuple<std::tuple_element_t<Is, Tup>...>{ std::move(std::get<Is>(tup))... };
}
};
template<template<class>class Filter, class T>
constexpr auto tuple_filter(T&& tup){
using impl = tuple_filter_impl<std::remove_cv_t<std::remove_reference_t<T>>, Filter>;
return impl::extract(std::forward<T>(tup), typename impl::index_seq{});
}
用例
const std::tuple<int, std::string, uint64_t> t;
tuple_filter<std::is_integral>(t) // std::tuple<int, uint64_t>
再帰&関数のオーバーロードで実行していた処理を簡潔に書ける。
struct Base{};
struct D1 : Base{
void func() const { std::cout << "D1" << std::endl; }
};
struct D2 : Base{
void func() const { std::cout << "D2" << std::endl; }
};
template<class T>
struct is_derived_from_Base : std::is_base_of<Base, std::remove_cv_t<std::remove_reference_t<T>>>{};
int main(){
std::tuple<D1, std::string, D2> t;
static_assert(std::is_same_v<decltype(tuple_filter<is_derived_from_Base>(t)), std::tuple<D1, D2>>);
std::apply(
[](auto&&... elms){ (elms.func(), ...); },
tuple_filter<is_derived_from_Base>(t)
);
}
いつ使うんだこれ?