从类型中提取模板模板的演绎指南
Deduction guide to extract template template from types
考虑以下 class:
// Class definition
template <template <class...> class... Templates>
class template_pack
{
public:
template <class... Types>
constexpr template_pack(const Types&...) noexcept;
};
// Class template argument deduction guide
template <class... Types>
template_pack(const Types&...) -> template_pack</* something here */>
我们假设 Types...
的形式是 template <class...> class... Templates
。我想要的是:
template_pack pack(std::vector<int>{}, std::list<double>{}, std::deque<char>{});
导致:
template_pack<std::vector, std::list, std::deque>;
如何实现?
类似的东西似乎有效
#include <iostream>
#include <vector>
#include <list>
#include <deque>
template<typename... TS>
struct Pack;
template<typename S, typename... TS>
struct Pack<S, TS...> {
S s;
Pack<TS...> ts;
static constexpr size_t size = Pack<TS...>::size + 1;
constexpr Pack(S&& s, TS&&... ts) noexcept
: s(s)
, ts(std::forward<TS>(ts)...)
{}
};
template<typename S>
struct Pack<S> {
S s;
static constexpr size_t size = 1;
constexpr Pack(S&& s) noexcept
: s(s)
{}
};
template<>
struct Pack<> {
static constexpr size_t size = 0;
};
template<typename... TS>
constexpr auto make_pack(TS&&... ts) noexcept {
return Pack<TS...>(std::forward<TS>(ts)...);
}
int main() {
auto empty_pack = make_pack();
std::cout << empty_pack.size << std::endl; // 0
auto vector_pack = make_pack(std::vector<int>{});
std::cout << vector_pack.size << std::endl; // 1
auto vector_list_deque_pack = make_pack(std::vector<int>{}, std::list<double>{}, std::deque<char>{});
std::cout << vector_list_deque_pack.size << std::endl; // 3
}
如果每个模板只有一个参数,您可以采用一种捷径:
template <template<class> class... Templates, class... Types>
template_pack(const Templates<Types>&...) -> template_pack<Templates...>;
每个参数只有一个参数,很容易在所有模板中拆分一个包。
不幸的是,如果事先不知道模板的数量,我不知道有什么方法可以为每个模板单独打包。因此,似乎需要一个通过助手的间接层。此外,演绎指南必须采用 -> template_pack<something>
形式,大概是为了避免让编译器做太多工作或 运行 成为不可能的问题。鉴于此,class 需要稍微调整一下:
template <template <class...> class... Templates>
class holder {};
// Class definition
template<class Holder>
class template_pack;
template <template <class...> class... Templates>
class template_pack<holder<Templates...>>
{
public:
template <class... Types>
constexpr template_pack(const Types&...) noexcept {}
};
通过这个调整,我们可以制作一个助手(可能会被简化得更直接一点):
template<template<class...> class... TTs>
struct result {
using type = holder<TTs...>;
};
template<class T>
struct type {};
template<class Prev, class Current, class... Rest>
auto helper() {
return []<template<class...> class... PrevTTs, template<class...> class CurrTT, class... CurrTs>(result<PrevTTs...>, type<CurrTT<CurrTs...>>) {
if constexpr (sizeof...(Rest) == 0) {
return result<PrevTTs..., CurrTT>{};
} else {
return helper<result<PrevTTs..., CurrTT>, Rest...>();
}
}(Prev{}, type<Current>{});
}
我使用 C++20 的模板化 lambda 将两个模板从它们的 arg 包内联分离出来,而不是有一个额外的辅助层,但是这个额外的层在早期的标准中仍然是可能的,只是更难看。助手递归地获取先前的结果,一次拆开一个模板,将其添加到结果中,然后递归地调用自身,直到没有参数为止。
有了这个帮手,就可以制作推导指南了:
// Class template argument deduction guide
template <typename... Ts>
template_pack(const Ts&...) -> template_pack<typename decltype(helper<result<>, Ts...>())::type>;
你可以找到一个full example here。也可以稍微显着改进此代码,但核心思想就在那里。
How to make that work?
我没有办法:总有一些事情是无法推导出来的。
不完全是你问的,但我能想象的最好的通过自定义类型特征ttw
(对于"template-template-wrapper")
template <template <typename...> class C>
struct ttw
{
template <typename ... Ts>
constexpr ttw (C<Ts...> const &)
{ }
};
使用隐式推导指南,从构造函数接收的类型中提取模板模板,并将其用作模板参数。
所以你可以用接收ttw<Templates>
的构造函数编写template_pack
template <template <typename...> class... Templates>
struct template_pack
{
constexpr template_pack (ttw<Templates> const & ...)
{ }
};
您可以按如下方式使用(同样:通过隐式演绎指南)
template_pack tp1 {ttw{std::vector<int>{}},
ttw{std::set<long>{}},
ttw{std::map<char, short>{}}};
问题是有必要明确地将参数包装在 ttw{}
中,因为举个例子,std::vector<int>
可以转换为 ttw<std::vector>
但不是 ttw<std::vector>
。因此,传递 std::vector{}
而不是 ttw{std::vector{}}
,我们遇到了无法推导出类型的常见 chicken/egg 问题,因为要推导出它,需要一个需要了解的转换我们要推断的类型。
显然,您可以要求显式 ttw
包装适用于特定的 make_template_pack()
函数
template <typename ... Ts>
constexpr auto make_template_pack (Ts && ... ts)
{ return template_pack{ttw{std::forward<Ts>(ts)}...}; }
下面是一个完整的编译示例
#include <map>
#include <set>
#include <vector>
#include <type_traits>
template <template <typename...> class C>
struct ttw
{
template <typename ... Ts>
constexpr ttw (C<Ts...> const &)
{ }
};
template <template <typename...> class... Templates>
struct template_pack
{
constexpr template_pack (ttw<Templates> const & ...)
{ }
};
template <typename ... Ts>
constexpr auto make_template_pack (Ts && ... ts)
{ return template_pack{ttw{std::forward<Ts>(ts)}...}; }
int main ()
{
template_pack tp1 {ttw{std::vector<int>{}},
ttw{std::set<long>{}},
ttw{std::map<char, short>{}}};
auto tp2 { make_template_pack(std::vector<long>{},
std::set<int>{},
std::map<char, short>{}) };
using t0 = template_pack<std::vector, std::set, std::map>;
using t1 = decltype(tp1);
using t2 = decltype(tp2);
static_assert( std::is_same<t0, t1>::value );
static_assert( std::is_same<t0, t2>::value );
}
我 "succeed" 具有额外的特征:
template <typename T> struct template_traits;
// Variadic case
template <template <class...> class C, typename ... Ts>
struct template_traits<C<Ts...>>
{
template <typename ... Us>
using template_type = C<Us...>;
};
然后,参数推导为:
// Class template argument deduction guide
template <class... Types>
template_pack(Types&&...)
-> template_pack<template_traits<std::decay_t<Types>>::template template_type...>;
问题是别名并不完全相同(gcc 认为某些别名是相同的
与 clang 相反)(我为那个 BTW 打开了一个 )
template_traits<std::vector>::template_type
不是 std::vector
即使对于任何 T
、A
、template_traits<std::vector>::template_type<T, A>
不是 std::vector<T, A>
.
考虑以下 class:
// Class definition
template <template <class...> class... Templates>
class template_pack
{
public:
template <class... Types>
constexpr template_pack(const Types&...) noexcept;
};
// Class template argument deduction guide
template <class... Types>
template_pack(const Types&...) -> template_pack</* something here */>
我们假设 Types...
的形式是 template <class...> class... Templates
。我想要的是:
template_pack pack(std::vector<int>{}, std::list<double>{}, std::deque<char>{});
导致:
template_pack<std::vector, std::list, std::deque>;
如何实现?
类似的东西似乎有效
#include <iostream>
#include <vector>
#include <list>
#include <deque>
template<typename... TS>
struct Pack;
template<typename S, typename... TS>
struct Pack<S, TS...> {
S s;
Pack<TS...> ts;
static constexpr size_t size = Pack<TS...>::size + 1;
constexpr Pack(S&& s, TS&&... ts) noexcept
: s(s)
, ts(std::forward<TS>(ts)...)
{}
};
template<typename S>
struct Pack<S> {
S s;
static constexpr size_t size = 1;
constexpr Pack(S&& s) noexcept
: s(s)
{}
};
template<>
struct Pack<> {
static constexpr size_t size = 0;
};
template<typename... TS>
constexpr auto make_pack(TS&&... ts) noexcept {
return Pack<TS...>(std::forward<TS>(ts)...);
}
int main() {
auto empty_pack = make_pack();
std::cout << empty_pack.size << std::endl; // 0
auto vector_pack = make_pack(std::vector<int>{});
std::cout << vector_pack.size << std::endl; // 1
auto vector_list_deque_pack = make_pack(std::vector<int>{}, std::list<double>{}, std::deque<char>{});
std::cout << vector_list_deque_pack.size << std::endl; // 3
}
如果每个模板只有一个参数,您可以采用一种捷径:
template <template<class> class... Templates, class... Types>
template_pack(const Templates<Types>&...) -> template_pack<Templates...>;
每个参数只有一个参数,很容易在所有模板中拆分一个包。
不幸的是,如果事先不知道模板的数量,我不知道有什么方法可以为每个模板单独打包。因此,似乎需要一个通过助手的间接层。此外,演绎指南必须采用 -> template_pack<something>
形式,大概是为了避免让编译器做太多工作或 运行 成为不可能的问题。鉴于此,class 需要稍微调整一下:
template <template <class...> class... Templates>
class holder {};
// Class definition
template<class Holder>
class template_pack;
template <template <class...> class... Templates>
class template_pack<holder<Templates...>>
{
public:
template <class... Types>
constexpr template_pack(const Types&...) noexcept {}
};
通过这个调整,我们可以制作一个助手(可能会被简化得更直接一点):
template<template<class...> class... TTs>
struct result {
using type = holder<TTs...>;
};
template<class T>
struct type {};
template<class Prev, class Current, class... Rest>
auto helper() {
return []<template<class...> class... PrevTTs, template<class...> class CurrTT, class... CurrTs>(result<PrevTTs...>, type<CurrTT<CurrTs...>>) {
if constexpr (sizeof...(Rest) == 0) {
return result<PrevTTs..., CurrTT>{};
} else {
return helper<result<PrevTTs..., CurrTT>, Rest...>();
}
}(Prev{}, type<Current>{});
}
我使用 C++20 的模板化 lambda 将两个模板从它们的 arg 包内联分离出来,而不是有一个额外的辅助层,但是这个额外的层在早期的标准中仍然是可能的,只是更难看。助手递归地获取先前的结果,一次拆开一个模板,将其添加到结果中,然后递归地调用自身,直到没有参数为止。
有了这个帮手,就可以制作推导指南了:
// Class template argument deduction guide
template <typename... Ts>
template_pack(const Ts&...) -> template_pack<typename decltype(helper<result<>, Ts...>())::type>;
你可以找到一个full example here。也可以稍微显着改进此代码,但核心思想就在那里。
How to make that work?
我没有办法:总有一些事情是无法推导出来的。
不完全是你问的,但我能想象的最好的通过自定义类型特征ttw
(对于"template-template-wrapper")
template <template <typename...> class C>
struct ttw
{
template <typename ... Ts>
constexpr ttw (C<Ts...> const &)
{ }
};
使用隐式推导指南,从构造函数接收的类型中提取模板模板,并将其用作模板参数。
所以你可以用接收ttw<Templates>
template_pack
template <template <typename...> class... Templates>
struct template_pack
{
constexpr template_pack (ttw<Templates> const & ...)
{ }
};
您可以按如下方式使用(同样:通过隐式演绎指南)
template_pack tp1 {ttw{std::vector<int>{}},
ttw{std::set<long>{}},
ttw{std::map<char, short>{}}};
问题是有必要明确地将参数包装在 ttw{}
中,因为举个例子,std::vector<int>
可以转换为 ttw<std::vector>
但不是 ttw<std::vector>
。因此,传递 std::vector{}
而不是 ttw{std::vector{}}
,我们遇到了无法推导出类型的常见 chicken/egg 问题,因为要推导出它,需要一个需要了解的转换我们要推断的类型。
显然,您可以要求显式 ttw
包装适用于特定的 make_template_pack()
函数
template <typename ... Ts>
constexpr auto make_template_pack (Ts && ... ts)
{ return template_pack{ttw{std::forward<Ts>(ts)}...}; }
下面是一个完整的编译示例
#include <map>
#include <set>
#include <vector>
#include <type_traits>
template <template <typename...> class C>
struct ttw
{
template <typename ... Ts>
constexpr ttw (C<Ts...> const &)
{ }
};
template <template <typename...> class... Templates>
struct template_pack
{
constexpr template_pack (ttw<Templates> const & ...)
{ }
};
template <typename ... Ts>
constexpr auto make_template_pack (Ts && ... ts)
{ return template_pack{ttw{std::forward<Ts>(ts)}...}; }
int main ()
{
template_pack tp1 {ttw{std::vector<int>{}},
ttw{std::set<long>{}},
ttw{std::map<char, short>{}}};
auto tp2 { make_template_pack(std::vector<long>{},
std::set<int>{},
std::map<char, short>{}) };
using t0 = template_pack<std::vector, std::set, std::map>;
using t1 = decltype(tp1);
using t2 = decltype(tp2);
static_assert( std::is_same<t0, t1>::value );
static_assert( std::is_same<t0, t2>::value );
}
我 "succeed" 具有额外的特征:
template <typename T> struct template_traits;
// Variadic case
template <template <class...> class C, typename ... Ts>
struct template_traits<C<Ts...>>
{
template <typename ... Us>
using template_type = C<Us...>;
};
然后,参数推导为:
// Class template argument deduction guide
template <class... Types>
template_pack(Types&&...)
-> template_pack<template_traits<std::decay_t<Types>>::template template_type...>;
问题是别名并不完全相同(gcc 认为某些别名是相同的
与 clang 相反)(我为那个 BTW 打开了一个
template_traits<std::vector>::template_type
不是 std::vector
即使对于任何 T
、A
、template_traits<std::vector>::template_type<T, A>
不是 std::vector<T, A>
.