【问题标题】:How does std::get work?std::get 是如何工作的?
【发布时间】:2013-06-15 05:39:13
【问题描述】:

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

tuple(Args&&... args);

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

【问题讨论】:

  • 你看过boost::getboost::tuple吗?你的std::get 实现的头文件怎么样?阅读起来并不容易,但通常这些都是用 C++ 实现的。 (标准不要求标准库用C++实现)

标签: c++ templates c++11 tuples stdtuple


【解决方案1】:

这是tuple-like 类的粗略玩具实现。

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

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

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

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&lt;n, T&gt; 中的哪一个。现在我们有了存储类型,我们只需返回数据字段:

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";
}

【讨论】:

  • 这可以大大改进。参见例如this toy implementation 我前段时间写的。它依赖于派生到基的转换,并通过指定基类型的一部分(即索引)来强制转换正确。
  • @Xeo 已修复,删除了 int(*)[N] hack。
【解决方案2】:

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

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

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

这已经允许我们定义一个空结构,tuple&lt;&gt; 数据,尽管这还不是很有用。

接下来是递归案例特化:

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 data(1, 2.1, "hello")。

那么发生了什么?首先,请注意这是一种专业化,它的要求是至少存在一个可变参数模板参数(即上面的T),而不关心包Rest 的具体构成。知道 T 存在允许定义其数据成员 first。其余数据递归打包为tuple&lt;Rest ... &gt;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&lt;int, float&gt; 数据并且我们需要data.get&lt;1&gt;()。这会调用helper&lt;1, tuple&lt;int, float&gt;&gt;::get(data)(第二个特化),然后又调用helper&lt;0, tuple&gt;::get(data.rest),最后返回(通过第一个特化,因为现在 idx 为 0)data.rest.first。

原来如此!这是整个功能代码,在主函数中使用了一些示例:

完整代码

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

Reference

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-12-15
    • 2017-10-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多