STL tuple源码分析

STL tuple源码分析

和pair一样,tuple也是STL中非常常见的数据结构。pair是个二元组,只支持两个类型参数,tuple则是个多元组,可以支持多个类型参数。因此,在具体实现上,要比pair复杂一些。我们还是以MSVC提供的源码为基础,对tuple进行分析。

tuple是多元组,意味着它的模板参数是不固定的,是一个可变参数模板类。STL提供了两种tuple,一种tuple是特化后无参的,一种是可变参数的。在可变参数模板类的实现里,只取出一个参数进行处理,使用继承的方式,把剩下的工作交给少了一个参数的模板父类去做。如此下去,最后就会走到无参版本的tuple,完成最后一步的工作。STL的设计还是非常巧妙的。

template <>
class tuple<> {};template <class _This, class... _Rest>
class tuple<_This, _Rest...> : private tuple<_Rest...> { // recursive tuple definition};

还是先从构造函数看起。无参版本的tuple构造函数都非常简单:

template <>
class tuple<> { // empty tuple
public:constexpr tuple() noexcept = default; /* strengthened */constexpr tuple(const tuple&) noexcept /* strengthened */ {} // TRANSITION, ABI: should be defaultedtemplate <class _Alloc>constexpr tuple(allocator_arg_t, const _Alloc&) noexcept /* strengthened */ {}template <class _Alloc>constexpr tuple(allocator_arg_t, const _Alloc&, const tuple&) noexcept /* strengthened */ {}template <class _Tag, enable_if_t<is_same_v<_Tag, _STD _Exact_args_t>, int> = 0>constexpr tuple(_Tag) noexcept /* strengthened */ {}template <class _Tag, class _Alloc, enable_if_t<is_same_v<_Tag, _STD _Alloc_exact_args_t>, int> = 0>constexpr tuple(_Tag, const _Alloc&) noexcept /* strengthened */ {}
};

可以看到构造函数中有一些形如_Exact_args_t_Alloc_exact_args_t的tag字段,它们是用来区分构造函数的用途的。如果没有这些tag,tuple构造的时候就会直接把每个参数都作为tuple的一部分进行处理。tag类型的定义很简单,就是一个空的struct,STL的注释中也标注了它们的用途。

struct _Exact_args_t {explicit _Exact_args_t() = default;
}; // tag type to disambiguate construction (from one arg per element)struct _Unpack_tuple_t {explicit _Unpack_tuple_t() = default;
}; // tag type to disambiguate construction (from unpacking a tuple/pair)struct _Alloc_exact_args_t {explicit _Alloc_exact_args_t() = default;
}; // tag type to disambiguate construction (from an allocator and one arg per element)struct _Alloc_unpack_tuple_t {explicit _Alloc_unpack_tuple_t() = default;
}; // tag type to disambiguate construction (from an allocator and unpacking a tuple/pair)

多参数版本的tuple构造函数要复杂得多了,首先是默认构造函数:

template <class _This2 = _This,enable_if_t<conjunction_v<_STD is_default_constructible<_This2>, _STD is_default_constructible<_Rest>...>,int>           = 0>
constexpr explicit(!conjunction_v<_Is_implicitly_default_constructible<_This2>, _Is_implicitly_default_constructible<_Rest>...>)tuple() noexcept(conjunction_v<is_nothrow_default_constructible<_This2>,is_nothrow_default_constructible<_Rest>...>) // strengthened: _Mybase(), _Myfirst() {}

enable_if_t限制了只有传入参数类型有默认构造函数时,这个tuple的默认构造函数才成立;explicit里的一坨代码则表示,只要传入参数类型中,有一个类型是必须explicit才能默认构造的,那么tuple也得是explicit默认构造的;noexcept里的一坨代码说明了只有传入参数类型的默认构造函数都不会抛出异常时,tuple的默认构造函数才不会抛出异常。这个吟唱的代码属实是有点长的,它具体的实现却是十分简洁,就是调用基类的默认构造函数,和取出的第一个参数类型的默认构造函数。

然后就是tuple最常用的构造函数了,所有传入的参数都视为tuple的一部分,传入的参数支持常量左值引用和右值引用。

template <class _This2 = _This, enable_if_t<_Tuple_constructible_v<tuple, const _This2&, const _Rest&...>, int> = 0>
constexpr explicit(_Tuple_conditional_explicit_v<tuple, const _This2&, const _Rest&...>) tuple(const _This& _This_arg, const _Rest&... _Rest_arg) noexcept(conjunction_v<is_nothrow_copy_constructible<_This2>,is_nothrow_copy_constructible<_Rest>...>) // strengthened: tuple(_Exact_args_t{}, _This_arg, _Rest_arg...) {}template <class _This2, class... _Rest2,enable_if_t<conjunction_v<_STD _Tuple_perfect_val<tuple, _This2, _Rest2...>,_STD _Tuple_constructible_val<tuple, _This2, _Rest2...>>,int> = 0>
constexpr explicit(_Tuple_conditional_explicit_v<tuple, _This2, _Rest2...>) tuple(_This2&& _This_arg,_Rest2&&... _Rest_arg) noexcept(_Tuple_nothrow_constructible_v<tuple, _This2, _Rest2...>) // strengthened: tuple(_Exact_args_t{}, _STD forward<_This2>(_This_arg), _STD forward<_Rest2>(_Rest_arg)...) {}

同样需要在编译期间检查构造函数是否合法。_Tuple_constructible_v就是做这个的,它长这样:

template <class _Dest, class... _Srcs>
_INLINE_VAR constexpr bool _Tuple_constructible_v =_Tuple_constructible_v0<tuple_size_v<_Dest> == sizeof...(_Srcs), _Dest, _Srcs...>;

可以看出_Tuple_constructible_v0的第一个模板参数是个bool值,要么为true要么为false:

template <bool _Same, class _Dest, class... _Srcs>
_INLINE_VAR constexpr bool _Tuple_constructible_v0 = false;template <class... _Dests, class... _Srcs>
_INLINE_VAR constexpr bool _Tuple_constructible_v0<true, tuple<_Dests...>, _Srcs...> =conjunction_v<is_constructible<_Dests, _Srcs>...>;

如果为false,则_Tuple_constructible_v0的值直接就是false了,如果为true,则当所有_Dests参数都可以被_Srcs参数所构造时,它的值才为true。那么这里其实就可以推断出来了,_Tuple_constructible_v首先判断tuple的size是否和传入参数的数量一致,如果不一致则构造函数非法;如果一致,进一步判断tuple的每个参数是否能用传入的参数进行构造,只有都满足条件时构造函数才合法。比如下面这个例子:

#include <iostream>
#include <utility>
#include <tuple>
#include <type_traits>using namespace std;class A
{};class B
{
public:B(int x) {}
};int main()
{tuple<int, int> t1(1, 2); // oktuple<int, int> t2(1); // errortuple<int, A> t3(1, 2); // errortuple<int, B> t4(1, 2); // okreturn 0;
}

_Tuple_conditional_explicit_v也差不多,用来判断构造函数是不是explicit的,只要tuple的某个参数是explicit的,那么tuple的构造函数就是explicit的。这里判断某个参数是否是explicit,只要判断传入参数能不能直接convert为目标参数就行了,两者其实是等价的。

我们注意到这里调用的构造函数是带有_Exact_args_t这个tag的,实际上真正执行构造的函数为

template <class _Tag, class _This2, class... _Rest2, enable_if_t<is_same_v<_Tag, _STD _Exact_args_t>, int> = 0>
constexpr tuple(_Tag, _This2&& _This_arg, _Rest2&&... _Rest_arg): _Mybase(_Exact_args_t{}, _STD forward<_Rest2>(_Rest_arg)...), _Myfirst(_STD forward<_This2>(_This_arg)) {}

利用这个tag,就可以逐一使用参数递归地构造完tuple了。这里的构造函数参数是右值引用,实现里又使用了forward转发,这样保证无论传入参数是左值引用还是右值引用都不会出现问题。

再来看看tuple的拷贝构造函数和移动构造函数:

tuple(const tuple&) = default;
tuple(tuple&&)      = default;template <class... _Other, enable_if_t<conjunction_v<_STD _Tuple_constructible_val<tuple, const _Other&...>,_STD _Tuple_convert_val<tuple, const tuple<_Other...>&, _Other...>>,int> = 0>
constexpr explicit(_Tuple_conditional_explicit_v<tuple, const _Other&...>)tuple(const tuple<_Other...>& _Right) noexcept(_Tuple_nothrow_constructible_v<tuple, const _Other&...>) // strengthened: tuple(_Unpack_tuple_t{}, _Right) {}template <class... _Other, enable_if_t<conjunction_v<_STD _Tuple_constructible_val<tuple, _Other...>,_STD _Tuple_convert_val<tuple, tuple<_Other...>, _Other...>>,int> = 0>
constexpr explicit(_Tuple_conditional_explicit_v<tuple, _Other...>)tuple(tuple<_Other...>&& _Right) noexcept(_Tuple_nothrow_constructible_v<tuple, _Other...>) // strengthened: tuple(_Unpack_tuple_t{}, _STD move(_Right)) {}

如果传入参数的类型和tuple的类型完全一致,那么直接让编译器合成一个就好了。如果不一致,则要进行参数检查,首先_Tuple_constructible_val判断传入tuple的每个参数是否可以拿来构造当前tuple的每个参数,它的作用和前面提到的_Tuple_constructible_v本质上是一样的,只不过由于这里用在了conjunction_v上,需要传入一个struct,struct的value表示要判断的内容:

template <class _Dest, class... _Srcs>
struct _Tuple_constructible_val : bool_constant<_Tuple_constructible_v<_Dest, _Srcs...>> {};

接下来,这里还有一个_Tuple_convert_val。它的定义看上去蛮奇怪的:

// Constrain tuple's converting constructors
template <class _Myself, class _OtherTuple, class... _Other>
struct _Tuple_convert_val : true_type {};template <class _This, class _OtherTuple, class _Uty>
struct _Tuple_convert_val<tuple<_This>, _OtherTuple, _Uty>: bool_constant<!disjunction_v<is_same<_This, _Uty>, is_constructible<_This, _OtherTuple>,is_convertible<_OtherTuple, _This>>> {};

如果tuple的元素个数不止一个,那结果直接就是true,而只有一个元素时,需要进行一系列的判断,首先两个tuple的类型不能相同,然后,传入的tuple参数不能直接构造tuple的参数类型,也不能直接convert到tuple的参数类型。只要有一个条件不满足,这个构造函数就非法了。字面意义上解释非常拗口,不如来看一个例子:

#include <iostream>
#include <utility>
#include <tuple>
#include <type_traits>using namespace std;class A
{
};class B
{public:B(const A&) { cout << "B(const A&)" << endl; }B(const tuple<A>&) { cout << "B(const tuple<A>&)" << endl; }
};int main()
{tuple<A> t1;tuple<B> t2(t1);cout << _Tuple_convert_val<tuple<B>, tuple<A>, A>::value << endl;tuple<A, A> t3;tuple<B, B> t4(t3);cout << _Tuple_convert_val<tuple<B, B>, tuple<A, A>, A, A>::value << endl;return 0;
}

例子中,B类包含一个const tuple<A>&的构造函数。换句话说,tuple<A>类型可以直接转换为B类型。此时拿tuple<A>类型去构造tuple<B>类型,那么_Tuple_convert_val的值应该为false,也就是拷贝构造函数和移动构造函数不再合法。但是我们实际运行这段代码,却发现编译不会报错,输出的结果如下:

>test.exe
B(const tuple<A>&)
0
B(const A&)
B(const A&)
1

_Tuple_convert_val的值的确为false,那为什么编译会通过呢?我们发现,B(const tuple<A>&)这个构造函数被调用了。也就是说,tuple<A>类型先通过这个构造函数,转换到了B类型,然后使用这个B类型完成了tuple<B>类型的构造。

拷贝构造函数和移动构造函数最终调用的是带有_Unpack_tuple_t这个tag的构造函数,这个tag指示tuple要把传入的tuple参数进行unpack,然后再进行构造:

template <class _Tag, class _Tpl, enable_if_t<is_same_v<_Tag, _STD _Unpack_tuple_t>, int> = 0>
constexpr tuple(_Tag, _Tpl&& _Right): tuple(_Unpack_tuple_t{}, _STD forward<_Tpl>(_Right),make_index_sequence<tuple_size_v<remove_reference_t<_Tpl>>>{}) {}

这个tag跟着构造函数继续传了下去,传给了更加通用的(可以指定具体unpack tuple的哪些元素)构造函数:

template <class _This, class... _Rest>
template <class _Tag, class _Tpl, size_t... _Indices, enable_if_t<is_same_v<_Tag, _STD _Unpack_tuple_t>, int> /* = 0 */>
constexpr tuple<_This, _Rest...>::tuple(_Tag, _Tpl&& _Right, index_sequence<_Indices...>): tuple(_Exact_args_t{}, _STD get<_Indices>(_STD forward<_Tpl>(_Right))...) {}

这个更加通用的构造函数最终内部调用回_Exact_args_t版本的构造函数。

tuple也接受pair类型的参数进行构造,对于pair类型,就是unpack出它的first和second,作为tuple的两个元素。例如:

#include <iostream>
#include <utility>
#include <tuple>
#include <type_traits>using namespace std;int main()
{pair<int, int> p1(1, 2);tuple<int, int> t1(p1);cout << get<0>(t1) << " " << get<1>(t1) << endl; // 1 2return 0;
}

tuple还支持带有自定义allocator的构造函数,这样传入的参数类型就可以使用这个allocator进行构造:

#include <iostream>
#include <utility>
#include <tuple>
#include <type_traits>
#include <vector>using namespace std;int main()
{using my_alloc = allocator<int>;vector<int, my_alloc> v{5, 1, my_alloc{}};tuple<int, vector<int, my_alloc>, double> t{allocator_arg, my_alloc{}, 42, v, -3.14};return 0;
}

这里tuple的第二个元素,vector在构造时,除了使用传入的参数v,还用到了自定义的allocator,my_alloc。这里用到的tuple构造函数定义如下:

template <class _Alloc, class _This2 = _This,enable_if_t<_Tuple_constructible_v<tuple, const _This2&, const _Rest&...>, int> = 0>
_CONSTEXPR20 explicit(_Tuple_conditional_explicit_v<tuple, const _This2&, const _Rest&...>)tuple(allocator_arg_t, const _Alloc& _Al, const _This& _This_arg, const _Rest&... _Rest_arg): tuple(_Alloc_exact_args_t{}, _Al, _This_arg, _Rest_arg...) {}

可以看到实际调用的是带有_Alloc_exact_args_t这个tag的构造函数:

template <class _Tag, class _Alloc, class _This2, class... _Rest2,enable_if_t<is_same_v<_Tag, _STD _Alloc_exact_args_t>, int> = 0>
constexpr tuple(_Tag, const _Alloc& _Al, _This2&& _This_arg, _Rest2&&... _Rest_arg): _Mybase(_Alloc_exact_args_t{}, _Al, _STD forward<_Rest2>(_Rest_arg)...),_Myfirst(_Al, allocator_arg, _STD forward<_This2>(_This_arg)) {}

这里其实就是递归了,每次只对一个参数进行构造,这也是使用继承来实现tuple的妙处。不过,我们传入的参数,有的是支持allocator的,例如vector,有的又是不支持的,例如int,double,这里是如何做到区分的呢?

答案就在这个_Myfirst上,它不是简单地对应第一个参数类型的value,而是一个_Tuple_val<_This>类型的。_Tuple_val是一个包装类:

template <class _Ty>
struct _Tuple_val { // stores each value in a tupleconstexpr _Tuple_val() : _Val() {}template <class _Other>constexpr _Tuple_val(_Other&& _Arg) : _Val(_STD forward<_Other>(_Arg)) {}template <class _Alloc, class... _Other, enable_if_t<!uses_allocator_v<_Ty, _Alloc>, int> = 0>constexpr _Tuple_val(const _Alloc&, allocator_arg_t, _Other&&... _Arg) : _Val(_STD forward<_Other>(_Arg)...) {}template <class _Alloc, class... _Other,enable_if_t<conjunction_v<_STD uses_allocator<_Ty, _Alloc>,_STD is_constructible<_Ty, _STD allocator_arg_t, const _Alloc&, _Other...>>,int> = 0>constexpr _Tuple_val(const _Alloc& _Al, allocator_arg_t, _Other&&... _Arg): _Val(allocator_arg, _Al, _STD forward<_Other>(_Arg)...) {}template <class _Alloc, class... _Other,enable_if_t<conjunction_v<_STD uses_allocator<_Ty, _Alloc>,_STD negation<_STD is_constructible<_Ty, _STD allocator_arg_t, const _Alloc&, _Other...>>>,int> = 0>constexpr _Tuple_val(const _Alloc& _Al, allocator_arg_t, _Other&&... _Arg): _Val(_STD forward<_Other>(_Arg)..., _Al) {}_Ty _Val;
};

从代码中得知,当参数类型不支持传入的allocator类型时,构造_Val就会使用不带allocator的版本,只有参数类型支持allocator时,带allocator版本的构造函数才会变得合法。

tuple也提供了一大堆赋值操作符的重载函数,不过内部实现基本一致,比如

template <class _Myself = tuple, class _This2 = _This,enable_if_t<conjunction_v<_STD _Is_copy_assignable_no_precondition_check<_This2>,_STD _Is_copy_assignable_no_precondition_check<_Rest>...>,int> = 0>
_CONSTEXPR20 tuple& operator=(_Identity_t<const _Myself&> _Right) noexcept(conjunction_v<is_nothrow_copy_assignable<_This2>, is_nothrow_copy_assignable<_Rest>...>) /* strengthened */ {_Myfirst._Val = _Right._Myfirst._Val;_Get_rest()   = _Right._Get_rest();return *this;
}

一样也是递归赋值,自身只处理_Myfirst的赋值,其余的就交给基类处理了。

由于pair只有两个元素,STL就直接暴露了first和second来访问它们。而tuple里的元素数量是不固定的,就得使用get函数来访问指定index的元素,STL对左值引用,右值引用,常量左值引用,常量右值引用各实现了一份get:

template <size_t _Index, class... _Types>
friend constexpr tuple_element_t<_Index, tuple<_Types...>>& get(tuple<_Types...>& _Tuple) noexcept;template <size_t _Index, class... _Types>
friend constexpr const tuple_element_t<_Index, tuple<_Types...>>& get(const tuple<_Types...>& _Tuple) noexcept;template <size_t _Index, class... _Types>
friend constexpr tuple_element_t<_Index, tuple<_Types...>>&& get(tuple<_Types...>&& _Tuple) noexcept;template <size_t _Index, class... _Types>
friend constexpr const tuple_element_t<_Index, tuple<_Types...>>&& get(const tuple<_Types...>&& _Tuple) noexcept;

常量右值引用算是非常少见了,对一个常量的左值进行move操作,返回的就是常量右值引用,比如下面这个例子:

#include <iostream>
#include <utility>
#include <tuple>
#include <type_traits>using namespace std;void f(int&& x) { cout << "rvalue" << endl; }
void f(const int&& x) { cout << "const rvalue" << endl; }int main()
{const int x = 1;f(1); // rvaluef(move(x)); // const rvaluereturn 0;
}

get函数的实现同样也十分地巧妙,利用模板找到从指定index开始的tuple基类,因为index之前的元素都在这个基类的子类里面,那么基类的_Myfirst就是要找的元素。接下来只要做下强制类型转换就结束了。

template <class _This, class... _Rest>
struct _MSVC_KNOWN_SEMANTICS tuple_element<0, tuple<_This, _Rest...>> { // select first elementusing type = _This;// MSVC assumes the meaning of _Ttype; remove or rename, but do not change semanticsusing _Ttype = tuple<_This, _Rest...>;
};template <size_t _Index, class _This, class... _Rest>
struct _MSVC_KNOWN_SEMANTICS tuple_element<_Index, tuple<_This, _Rest...>>: tuple_element<_Index - 1, tuple<_Rest...>> {}; // recursive tuple_element definition_EXPORT_STD template <size_t _Index, class _Tuple>
using tuple_element_t = typename tuple_element<_Index, _Tuple>::type;_EXPORT_STD template <size_t _Index, class... _Types>
_NODISCARD constexpr tuple_element_t<_Index, tuple<_Types...>>& get(tuple<_Types...>& _Tuple) noexcept {using _Ttype = typename tuple_element<_Index, tuple<_Types...>>::_Ttype;return static_cast<_Ttype&>(_Tuple)._Myfirst._Val;
}

C++14之后,tuple还提供了根据参数类型来寻找tuple元素的get函数:

template <class _Ty, class... _Types>
friend constexpr _Ty& get(tuple<_Types...>& _Tuple) noexcept;template <class _Ty, class... _Types>
friend constexpr const _Ty& get(const tuple<_Types...>& _Tuple) noexcept;template <class _Ty, class... _Types>
friend constexpr _Ty&& get(tuple<_Types...>&& _Tuple) noexcept;template <class _Ty, class... _Types>
friend constexpr const _Ty&& get(const tuple<_Types...>&& _Tuple) noexcept;

get函数只能使用tuple中唯一的参数类型,如果不止一个参数的类型相同,或者没有这样的参数类型,编译时就会报错:

#include <iostream>
#include <utility>
#include <tuple>
#include <type_traits>using namespace std;int main()
{tuple<int, int, double> t(1, 2, 3.0);cout << get<double>(t) << endl; // okcout << get<int>(t) << endl; // errorcout << get<float>(t) << endl; // errorreturn 0;
}

背后的实现也类似,找到第一个符合的元素类型的tuple基类,强制类型转换取出_Myfirst即可。只不过,需要对参数类型进行编译期检查,保证tuple中符合的参数类型只有一个。

template <class _This, class... _Rest>
struct _Tuple_element<_This, tuple<_This, _Rest...>> { // select first elementstatic_assert(!_Is_any_of_v<_This, _Rest...>, "duplicate type T in get<T>(tuple)");using _Ttype = tuple<_This, _Rest...>;
};template <class _Ty, class _This, class... _Rest>
struct _Tuple_element<_Ty, tuple<_This, _Rest...>> { // recursive _Tuple_element definitionusing _Ttype = typename _Tuple_element<_Ty, tuple<_Rest...>>::_Ttype;
};_EXPORT_STD template <class _Ty, class... _Types>
_NODISCARD constexpr _Ty& get(tuple<_Types...>& _Tuple) noexcept {using _Ttype = typename _Tuple_element<_Ty, tuple<_Types...>>::_Ttype;return static_cast<_Ttype&>(_Tuple)._Myfirst._Val;
}

C++17之后开始支持类模板参数推导(Class Template Argument Deduction),tuple这里也做了推导指引:

template <class... _Types>
tuple(_Types...) -> tuple<_Types...>;template <class _Ty1, class _Ty2>
tuple(pair<_Ty1, _Ty2>) -> tuple<_Ty1, _Ty2>;template <class _Alloc, class... _Types>
tuple(allocator_arg_t, _Alloc, _Types...) -> tuple<_Types...>;template <class _Alloc, class _Ty1, class _Ty2>
tuple(allocator_arg_t, _Alloc, pair<_Ty1, _Ty2>) -> tuple<_Ty1, _Ty2>;template <class _Alloc, class... _Types>
tuple(allocator_arg_t, _Alloc, tuple<_Types...>) -> tuple<_Types...>;

这样写代码的时候就不必显式声明tuple的参数类型了:

#include <iostream>
#include <utility>
#include <tuple>
#include <type_traits>
#include <vector>using namespace std;int main()
{tuple t1{1, 2, 3, 4, 5};tuple t2{pair<int, int>(1, 2)};using my_alloc = allocator<int>;vector<int, my_alloc> v{5, 1, my_alloc{}};tuple t3{allocator_arg, my_alloc{}, 42, v, -3.14};tuple t4{allocator_arg, my_alloc{}, pair<int, int>(1, 2)};tuple t5{allocator_arg, my_alloc{}, t1};return 0;
}

STL还提供了两个便捷的函数创建tuple,一个是make_tuple,一个是tie,两者的区别在于返回的tuple类型有差异,一个返回参数类型为值类型的tuple(除非传入的参数是reference_wrapper),一个返回的是引用类型的tuple。换言之一个修改tuple自身的值不会影响传入的参数,另一个传入参数也被修改了。

_EXPORT_STD template <class... _Types>
_NODISCARD constexpr tuple<_Unrefwrap_t<_Types>...> make_tuple(_Types&&... _Args) { // make tuple from elementsusing _Ttype = tuple<_Unrefwrap_t<_Types>...>;return _Ttype(_STD forward<_Types>(_Args)...);
}_EXPORT_STD template <class... _Types>
_NODISCARD constexpr tuple<_Types&...> tie(_Types&... _Args) noexcept { // make tuple from elementsusing _Ttype = tuple<_Types&...>;return _Ttype(_Args...);
}

配合例子一起食用更佳:

#include <iostream>
#include <utility>
#include <tuple>
#include <type_traits>using namespace std;int main()
{int x = 1, y = 2, z = 3;auto t1 = make_tuple(x, y, z);get<0>(t1) = 10;get<1>(t1) = 20;get<2>(t1) = 30;cout << x << " " << y << " " << z << endl; // 1 2 3auto t2 = tie(x, y, z);get<0>(t2) = 100;get<1>(t2) = 200;get<2>(t2) = 300;cout << x << " " << y << " " << z << endl; // 100 200 300auto t3 = make_tuple(ref(x), ref(y), ref(z));get<0>(t3) = 1000;get<1>(t3) = 2000;get<2>(t3) = 3000;cout << x << " " << y << " " << z << endl; // 1000 2000 3000return 0;
}

最后,STL中没有提供打印输出tuple的函数,如果直接使用标准输出操作符<<,编译会报错:

#include <iostream>
#include <utility>
#include <tuple>
#include <type_traits>using namespace std;int main()
{int x = 1, y = 2, z = 3;auto t = make_tuple(x, y, z);cout << t << endl; // errorreturn 0;
}

当然,我们可以自己手写一个,实现tuple的输出。但是这里很容易想当然地写出一个错误的版本:

template<typename... Args>
ostream& operator<<(ostream& os, tuple<Args...> t)
{os << "(";os << get<0>(t);for (int i = 1; i < tuple_size<tuple<Args...>>::value; ++i)os << ", " << get<i>(t); // erroros << ")";return os;
}

很不幸编译会报错,原因是tuple设计之初就不是寻常的容器,它本身是不允许迭代元素的,意味着必须要在编译期间就得确定元素的索引值,运行期间传入一个动态的索引值i是不被允许的。

那么要怎么才能输出一个tuple呢?我们可以参考STL的思路,用template metaprogramming实现:

#include <iostream>
#include <utility>
#include <tuple>
#include <type_traits>using namespace std;template<int I, int N, typename... Args>
struct Print_Tuple
{static void print(ostream& os, const tuple<Args...>& t){os << get<I>(t) << (I + 1 == N ? "" : ", ");Print_Tuple<I + 1, N, Args...>::print(os, t);}
};template<int N, typename... Args>
struct Print_Tuple<N, N, Args...>
{static void print(ostream& os, const tuple<Args...>& t){}
};template<typename... Args>
ostream& operator<<(ostream& os, tuple<Args...> t)
{os << "(";Print_Tuple<0, sizeof...(Args), Args...>::print(os, t);os << ")";return os;
}int main()
{int x = 1, y = 2, z = 3;auto t = make_tuple(x, y, z);cout << t << endl; // (1, 2, 3)return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/621371.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

js检测网址是否可访问,javascript检测网址是否可访问,支持跨域;

js检测网址是否可访问&#xff0c;javascript检测网址是否可访问&#xff0c;支持跨域&#xff1b; <!DOCTYPE html> <html> <head><meta name"viewport" content"widthdevice-width" /><title>url检测是否可访问</tit…

从生活入手学编程(1):Edge浏览器设置自动刷新专业教程

一、前言 我们都知道&#xff0c;Edge浏览器运行时的速度卡的实在是感人…… 于是今天&#xff0c;我就突发奇想&#xff0c;来看一看怎么刷新并且还能保留页面内容。 二、探索 首先&#xff0c;我在此提醒您&#xff0c;在使用这种方法时要非常小心。因为更改网页源代…

MySQL——深入数据库原理(事务及锁)

文章目录 锁行级锁共享 (S) 锁排他 (X) 锁间隙锁 表级锁意向锁自增锁Lock Table/DDL 事务ACID 原则1. 原子性 A2. 一致性 C3. 隔离性 I4. 持久性 D 隔离级别1. READ UNCOMMITTED&#xff08;未提交读&#xff09;2. READ COMMITTED&#xff08;提交读&#xff09;3. REPEATABLE…

webpack魔法注释-预获取/预加载模块

Webpack v4.6.0 增加了对预获取&#xff08;prefetch&#xff09;和预加载&#xff08;preload&#xff09;的支持。 在声明 import 时&#xff0c;使用下面这些内置指令&#xff0c;可以让 webpack 输出“resource hint”&#xff0c;来告知浏览器&#xff1a; prefetch&…

【JavaWeb】Web程序设计期末复习总结

试题 一. 单选题&#xff08;共24题&#xff0c;24分&#xff09; 二. 多选题&#xff08;共16题&#xff0c;32分&#xff09; 三. 填空题&#xff08;共20题&#xff0c;10分&#xff09; 四. 判断题&#xff08;共30题&#xff0c;15分&#xff09; 五. 论述题&#xf…

基于TOGAF和WAF的企业级架构

TOGAF是技术无关的企业级架构框架&#xff0c;WAF则侧重于云的最佳实践和指导方针&#xff0c;本文介绍了两者的异同。原文: Enterprise Architecture with TOGAF and Well-Architected Frameworks (AWS, Azure and Google) 简介 本文旨在解释TOGAF和云架构框架/良好架构框架(W…

IaC基础设施即代码:使用Terraform 连接 alicloud阿里云

目录 一、实验 1.环境 2.alicloud阿里云创建用户 3.Linux使用Terraform 连接 alicloud 4.Windows使用Terraform 连接 alicloud 二、问题 1.Windows如何申明RAM 相关变量 2.Linux如何申明RAM 相关变量 3. Linux terraform 初始化失败 4.Linux terraform 计划与预览失败…

【LeetCode: 57. 插入区间+分类讨论+模拟】

&#x1f680; 算法题 &#x1f680; &#x1f332; 算法刷题专栏 | 面试必备算法 | 面试高频算法 &#x1f340; &#x1f332; 越难的东西,越要努力坚持&#xff0c;因为它具有很高的价值&#xff0c;算法就是这样✨ &#x1f332; 作者简介&#xff1a;硕风和炜&#xff0c;…

blender 导入到 Marvelous Designer

1&#xff09; 将模型的所有部分合并为一个单独的mesh 2&#xff09; 先调整计量单位&#xff1a; 3&#xff09;等比缩放&#xff0c;身高调整到180cm左右 4&#xff09;应用当前scale 首先&#xff0c;选中你要修改的物体&#xff0c;然后按下Ctrl-A键&#xff0c;打开应用…

CSAPP - bomblab 作弊方式2: gdb jump 命令, 以及修改 jne 为 nop 指令

CSAPP - bomblab 作弊方式2&#xff1a; gdb jump 命令&#xff0c; 以及修改 jne 为 nop 指令 厌倦了在 gdb 中一步步顺序执行 bomb 可执行程序。为什么不能自行控制程序的执行呢&#xff1f;跳到特定的函数去执行&#xff0c;又或者把原本要执行的指令改掉&#xff0c;gdb 里…

【Machine Learning】Other Stuff

本笔记基于清华大学《机器学习》的课程讲义中有关机器学习的此前未提到的部分&#xff0c;基本为笔者在考试前一两天所作的Cheat Sheet。内容较多&#xff0c;并不详细&#xff0c;主要作为复习和记忆的资料。 Robust Machine Learning Attack: PGD max ⁡ δ ∈ Δ L o s s (…

linux环境下安装postgresql

PostgreSQL: Linux downloads (Red Hat family)postgresql官网 PostgreSQL: Linux downloads (Red Hat family) 环境&#xff1a; centos7 postgresql14 选择版本 执行启动命令 配置远程连接文件 vi /var/lib/pqsql/14/data/postgresql.conf 这里将listen_addresses值由lo…

力扣399. 除法求值

广度优先搜索 思路&#xff1a; 题目梳理 输入信息里包含 A / B val&#xff1b;输入一堆 A / B 需要求出它们的比值&#xff1b;以操作数为节点&#xff0c;构建权重图&#xff0c;节点对应了关联节点及其比值&#xff1b;可以进行简单的处理&#xff0c;将代数变量映射为整…

【位运算】【二分查找】【C++算法】100160价值和小于等于 K 的最大数字

作者推荐 【动态规划】【字符串】扰乱字符串 本文涉及的基础知识点 二分查找算法合集 位运算 LeetCode100160. 价值和小于等于 K 的最大数字 给你一个整数 k 和一个整数 x 。 令 s 为整数 num 的下标从1 开始的二进制表示。我们说一个整数 num 的 价值 是满足 i % x 0 且…

数据分析---SQL(3)

目录 IF和CASE WHEN的区别IF函数示例CASE WHEN示例如何求字段整体的标准差和均值什么是笛卡尔积笛卡尔积通常的使用场景是什么rank、dense rank、row number的区别rank函数示例dense rank函数示例row number函数示例什么是窗口函数除了rank之外还有哪些窗口函数IF和CASE WHEN的…

阿里云ingress配置时间超时的参数

一、背景 在使用阿里云k8s集群的时候&#xff0c;内网API网关&#xff0c;刚开始是用的是Nginx&#xff0c;后面又搭建了ingress。 区别于nginx配置&#xff0c;ingress又该怎么设置参数呢&#xff1f;比如http超时时间等等。 本文会先梳理nginx是如何配置&#xff0c;再对比…

优雅的删除链表元

王有志&#xff0c;一个分享硬核Java技术的互金摸鱼侠加入Java人的提桶跑路群&#xff1a;共同富裕的Java人 在数据结构&#xff1a;链表中&#xff0c;我们实现了链表的删除方法&#xff0c;但代码看起来并不“优雅”&#xff0c;那么今天我们就来尝试使用多种方法&#xff0c…

Halcon 3D相关算子(二)

(1) moments_object_model_3d( : : ObjectModel3D, MomentsToCalculate : Moments) 功能&#xff1a;计算3D对象模型的平均值或中心二阶矩。要计算3D物体模型点的平均值&#xff0c;在MomentsToCalculate中选择mean_points&#xff1b;如果要计算二阶中心矩&#xff0c;则选择…

windows安装conda环境,开发openai应用准备,运行第一个ai程序

文章目录 前言一、windows创建openai开发环境二、国内代理方式访问openai的方法&#xff08;简单方法&#xff09;三、测试运行第一个openai程序总结 前言 作者开发第一个openai应用的环境准备、第一个openai程序调用成功&#xff0c;做个记录&#xff0c;希望帮助新来的你。 …

如何处理Uniapp中的异步请求?

在Uniapp中处理异步请求有以下几种方法&#xff1a; 使用 uni.request 方法发送异步请求&#xff0c;该方法返回一个 Promise 对象&#xff0c;可以使用 then 方法处理请求成功的回调&#xff0c;使用 catch 方法处理请求失败的回调。 uni.request({url: http://api.example.…