如何在可变模板上生成递归数据结构

How do I generate recursive data structures on variadic templates?

本文关键字:递归 数据结构      更新时间:2023-10-16

我试图掌握递归数据结构生成技术使用TMP解决这个问题。

假设我有一个可变模板 template<typename... Ts> struct my_sets { };

my_sets中,我想生成一个新的类型,其数据成员依赖于Ts

例如,我希望my_setsTs...

中的每个元素/类型都有一个std::set<T>数据成员
using x_t = my_sets<int,char,std::string>;
x_t x;
x.insert<0>(   5   );  // into a std::set<int> member in my_sets<>
x.insert<1>(  'z'  );  // into a std::set<char> member in my_sets<>
x.insert<2>( "foo" );  // into a std::set<std::string> member in my_sets<>

我认为实现这一点的一种方法可能是通过使用子类化和递归,但我不确定。

当然,如果通过自由函数或普通函数重载实现mutator更直接,也可以:

insert<0>( x,   5   );  // into a std::set<int> member in my_sets<>
insert<1>( x,  'z'  );  // into a std::set<char> member in my_sets<>
insert<2>( x, "foo" );  // into a std::set<std::string> member in my_sets<>

这里std::tuple有什么问题?

#include <tuple>
#include <set>
template<class... Ts>
using my_sets = std::tuple<std::set<Ts>...>;
// ...
auto x = my_sets<int, char, std::string>;
std::get<0>(x).insert(5);
std::get<1>(x).insert('z');
std::get<2>(x).insert("foo");

为外观添加一个免费的insert函数:

#include <utility>
template<std::size_t I, class SetTuple, class Arg>
auto insert(SetTuple& st, Arg&& arg)
  -> decltype(std::get<I>(st).insert(std::forward<Arg>(arg)))
{
  return std::get<I>(st).insert(std::forward<Arg>(arg));
}

@Xeo有一个优雅而简单的解决方案。但是,如果您希望将insert<>作为成员函数,则可以使用以下方法:

#include <set>
#include <tuple>
template<typename... Ts>
struct my_sets : protected std::set<Ts>...
{
    using types = std::tuple<Ts...>;
    template<int I, typename T>
    typename std::pair<
        typename std::set<typename std::tuple_element<I, types>::type>::iterator,
        bool> insert(T&& t)
    {
        return std::set<typename std::tuple_element<I, types>::type>::insert(
               std::forward<T>(t)
               );
    }
    // ...
    // Function for retrieving each set...
    template<int I>
    typename std::set<typename std::tuple_element<I, types>::type>& get()
    {
        return *this;
    }
};

你可以这样使用

#include <string>
int main()
{
    my_sets<int, double, std::string> s;
    s.insert<0>(42);
    s.insert<1>(3.14);
    s.insert<2>("Hello World!");
    s.get<0>().insert(42);
}
请注意,上面的解决方案不允许在类型列表中多次出现相同的类型(这可能是也可能不是期望的),尽管它可以很容易地扩展为允许它们:
#include <set>
#include <tuple>
namespace detail
{
    template<int... Is>
    struct indices
    {
        typedef indices<Is..., sizeof...(Is)> next;
    };
    template<int I>
    struct index_range
    {
        using type = typename index_range<I - 1>::type::next;
    };
    template<>
    struct index_range<0>
    {
        using type = indices<>;
    };
    template<int I, typename T>
    struct dummy : T { };
    template<typename, typename... Ts>
    struct my_sets { };
    template<int... Is, typename... Ts>
    struct my_sets<indices<Is...>, Ts...> : protected dummy<Is, std::set<Ts>>...
    {
        using types = std::tuple<Ts...>;
        template<int I, typename T>
        typename std::pair<
            typename std::set<typename std::tuple_element<I, types>::type>::iterator,
            bool
            > insert(T&& t)
        {
            return dummy<I, std::set<typename std::tuple_element<I, types>::type>>::
                insert(std::forward<T>(t));
        }
        template<int I>
        dummy<I, std::set<typename std::tuple_element<I, types>::type>>& get()
        {
            return *this;
        }
    };
}
template<typename... Ts>
using my_sets = detail::my_sets<
    typename detail::index_range<sizeof...(Ts)>::type,
    Ts...
    >;

你可以这样使用它:

#include <string>
int main()
{
    my_sets<int, double, int, std::string> s;
    s.insert<0>(42);
    s.insert<1>(3.14);
    s.insert<2>(1729);
    s.insert<3>("Hello World!");
    s.get<0>().insert(42);
}