从类型中提取模板模板的演绎指南

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...>;

Demo

问题是别名并不完全相同(gcc 认为某些别名是相同的 与 clang 相反)(我为那个 BTW 打开了一个

template_traits<std::vector>::template_type 不是 std::vector 即使对于任何 TAtemplate_traits<std::vector>::template_type<T, A> 不是 std::vector<T, A>.