从类型中提取模板模板的推导指南

时间:2019-05-05 22:59:27

标签: c++ c++17 template-meta-programming template-deduction template-templates

请考虑以下课程:

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

该如何工作?

4 个答案:

答案 0 :(得分:3)

  

该如何工作?

我看不出有什么办法:永远都无法推论。

并非完全是您的要求,但我能想象得到的最好的是通过自定义类型特征ttw(用于“ template-template-wrapper”)

template <template <typename...> class C>
struct ttw
 { 
   template <typename ... Ts>
   constexpr ttw (C<Ts...> const &) 
    { }
 };

使用隐式推导,从构造函数接收的类型中提取模板模板,并将其用作模板参数。

因此,您可以使用接收template_pack的构造函数编写ttw<Templates>

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>但不是{{1} }。因此,通过传递ttw<std::vector>而不是std::vector{},我们遇到了无法推论出的常见的鸡/蛋问题,因为要推论它,需要将其转换为对我们要演绎。

显然,您可以要求对特定的ttw{std::vector{}}函数进行显式的ttw包装工作

make_template_pack()

以下是完整的编译示例

template <typename ... Ts>
constexpr auto make_template_pack (Ts && ... ts)
 { return template_pack{ttw{std::forward<Ts>(ts)}...}; }

答案 1 :(得分:2)

我“成功”具有其他特征:

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打开了question

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

答案 2 :(得分:1)

如果每个模板只有一个参数,则可以使用一种快捷方式:

template <template<class> class... Templates, class... Types>
template_pack(const Templates<Types>&...) -> template_pack<Templates...>;

每个参数只有一个,可以很容易地在所有模板中拆分一个包。

不幸的是,我不知道在没有预先知道模板数量的情况下每个模板单独打包的任何方法。因此,似乎需要通过辅助程序进行的间接层。另外,推论指南必须采用-> template_pack<something>的形式,以免避免编译器做过多的工作或遇到不可能的问题。鉴于此,该类需要进行一些微调:

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。也许也可以对此代码进行一些显着的改进,但是核心思想就在那里。

答案 3 :(得分:0)

类似的东西似乎起作用

#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
}