7

在尝试自己制作一个std::get<N>(std::tuple)方法之后,我不太确定它是如何由编译器实现的。我知道std::tuple有这样的构造函数,

tuple(Args&&... args);

但具体args...分配给什么?我认为这对于了解如何工作很有用,std::get因为需要将参数放置在某个地方才能访问它们......

4

2 回答 2

12

这是一个类似tuple类的粗略玩具实现。

首先,一些元编程样板,用于表示整数序列:

template<int...> struct seq {};
template<int max, int... s> struct make_seq:make_seq< max-1, max-1, s... > {};
template<int... s> struct make_seq<0, s...> {
  typedef seq<s...> type;
};
template<int max> using MakeSeq = typename make_seq<max>::type;

接下来,实际存储数据的标记类:

template<int x, typename Arg>
struct foo_storage {
  Arg data;
};

每当我们想在编译时将数据与某个标记(在本例中为整数)相关联时,这种标记技术是一种常见的模式。标签(an inthere)通常不在存储中的任何地方使用,它仅用于标记存储。

foo_helper将一个序列和一组参数解包成一堆foo_storage,并以线性方式从它们继承。这是一种非常常见的模式——如果你经常这样做,你最终会创建为你做这件事的元编程工具:

template<typename Seq, typename... Args>
struct foo_helper {};
template<int s0, int... s, typename A0, typename... Args>
struct foo_helper<seq<s0, s...>, A0, Args...>:
  foo_storage<s0, A0>,
  foo_helper<seq<s...>, Args...>
{};

我的粗略tuple类型,foo创建了一个包含一系列索引和参数的包,并将其传递给上面的助手。助手然后创建一堆包含父类的标记数据:

template<typename... Args>
struct foo: foo_helper< MakeSeq<sizeof...(Args)>, Args... > {};

我从 的主体中删除了所有内容foo,因为不需要实现get.

get非常简单:我们采用存储类型(不是元组类型),并且显式template参数N消除了foo_storage<n, T>我们要访问的歧义。现在我们有了存储类型,我们只需返回数据字段:

template<int N, typename T>
T& get( foo_storage<N, T>& f )
 { return f.data; }
template<int N, typename T>
T const& get( foo_storage<N, T> const& f )
 { return f.data; }

我们正在使用 C++ 语言的重载机制来完成繁重的工作。当您使用类实例调用函数时,该实例作为每个父类都将检查是否可以使它们中的任何一个匹配。在N固定的情况下,只有一个父类是有效参数,因此父类(因此T)被自动推导。

最后,一些基本的测试代码:

#include <iostream>

int main() {
  foo<int, double> f;
  get<0>( f ) = 7;
  get<1>( f ) = 3.14;
  std::cout << get<0>(f) << "," << get<1>(f) << "\n";
}
于 2013-06-18T21:00:07.227 回答
0

定义具有可变数量和类型的数据成员的类或结构通常很有用,这些数据成员是在编译时定义的。典型示例是 std::tuple,但有时需要定义自己的自定义结构。这是一个使用复合定义结构的示例(而不是像std::tuple.

从一般(空)定义开始,它也可作为后续专业化中递归终止的基本情况:

template<typename ... T>
struct tuple {};

这已经允许我们定义一个空结构,tuple<>数据,尽管它还不是很有用。

接下来是递归案例专业化:

template<typename T, typename ... Rest>
struct tuple<T, Rest ...>
{
    tuple(const T& first, const Rest& ... rest)
        : first(first)
        , rest(rest...)
    {}
    
    T first;                                
    tuple<Rest ... > rest;
};

现在这足以让我们创建任意数据结构,例如 tuple<int, float, std::string> data(1, 2.1, "hello")。

发生什么了?首先,请注意,这是一种专业化,其要求是至少T存在一个可变参数模板参数(即上述),而不关心 pack 的具体构成Rest。知道 T 存在允许定义它的数据成员,first。其余数据递归打包为tuple<Rest ... >rest。构造函数初始化这两个成员,包括对 rest 成员的递归构造函数调用。

您可以将其可视化如下:

tuple <int, float>
   -> int first
   -> tuple <float> rest
         -> float first
         -> tuple <> rest
              -> (empty)

等到助手类。这次我们需要一个空的前向声明和两个专业化。首先声明:

template<size_t idx, typename T>
struct helper;

现在是基本情况(当idx==0)。在这种情况下,我们只返回第一个成员:

template<typename T, typename ... Rest>
struct helper<0, tuple<T, Rest ... >>
{
    static T get(tuple<T, Rest...>& data)
    {
        return data.first;
    }
};

在递归的情况下,我们递减 idx 并为其余成员调用助手:

template<size_t idx, typename T, typename ... Rest>
struct helper<idx, tuple<T, Rest ... >>
{
    static auto get(tuple<T, Rest...>& data)
    {
        return helper<idx-1, tuple<Rest ...>>::get(data.rest);
    }
};

举例来说,假设我们有tuple<int, float>数据并且我们需要data.get<1>(). 这会调用helper<1, tuple<int, float>>::get(data)(第二个专业化),然后调用helper<0, tuple>::get(data.rest),最终返回(通过第一个专业化,因为现在 idx 为 0)data.rest.first。

就是这样了!这是整个功能代码,在 main 函数中使用了一些示例:

完整代码

#include <type_traits>
#include <iostream>

using namespace std;

namespace my {
  template <typename ...Ts>
  struct tuple {};

  template <typename T, typename ...Ts>
  struct tuple <T, Ts...> {
    tuple(T first, Ts... rest) : 
    first(first), rest(rest...){}
    
    T first;

    tuple<Ts...> rest;
  };

  namespace detail {
    template <int N, typename ...Ts>
    struct helper;

    template <typename T, typename ...Ts>
    struct helper <0, tuple<T, Ts...>> {
      static auto get(tuple<T, Ts...> ds){
        return ds.first;
      }
    };

    template <int N, typename T, typename ...Ts>
    struct helper <N, tuple<T, Ts...>> {
      static auto get(tuple<T, Ts...> ds){
        return helper<N-1, tuple<Ts...>>::get(ds.rest);
      }
    };
  }

  template <int N, typename ...Ts>
  auto get(tuple<Ts...> ds){
    return detail::helper<N, decltype(ds)>::get(ds);
  }
}

int main(){
  my::tuple <int, bool, float> test = {5, false, 10.5};

  std::cout << my::get<0>(test) << endl;

  std::cout << my::get<1>(test) << endl;

  std::cout << my::get<2>(test) << endl;
}

参考

于 2022-01-20T02:44:12.113 回答