尝试自己制作std::get<N>
(std::tuple)
方法之后,我不太确定它是如何由编译器实现的。我知道std::tuple
有一个像这样的构造函数,
tuple(Args&&... args);
但是args...
到底分配给了什么?我认为这有助于了解std::get
如何工作,因为需要将参数放在某处以便访问它们...
答案 0 :(得分:8)
以下是类似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;
};
每当我们想要在编译时将数据与某个标记相关联时(这种情况下,整数),这种标记技术就是一种常见的模式。标签(此处为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
创建了一系列索引和args的包,并将其传递给上面的帮助程序。然后帮助程序创建一组包含父类的标记数据:
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";
}