std::make_from_tuple 源码分析
背景:
std::make_from_tuple 配合tuple 可以类参数构造,这个功能在c++17中还算常用,接下来分析下其内部原理
std::make_from_tuple 用例
C++ #include <iostream> #include <tuple> struct Foo { Foo(int first, float second, int third) { std::cout << first << ", " << second << ", " << third << "\n"; } }; int main() { auto tuple = std::make_tuple(42, 3.14f, 0); std::make_from_tuple<Foo>(std::move(tuple)); } |
Output
std::make_from_tuple 源码解析
C++ template <class _Tp, class _Tuple> inline constexpr _Tp make_from_tuple(_Tuple&& __t)
_VSTD::__make_from_tuple_impl<_Tp>(_VSTD::forward<_Tuple>(__t), typename __make_tuple_indices<tuple_size_v<remove_reference_t<_Tuple>>>::type{}) |
可以看的出来make_from_tuple 是一个函数模版, 并且用constexpr 修饰,说明在编译期做了这个事情
紧接着下边使用了__make_from_tuple_impl 进行调用,typename __make_tuple_indices<tuple_size_v<remove_reference_t<_Tuple>>>::type{} 这个也是关键的地方,把tuple里边的数据整理成__tuple_indices<_Idx...>,以供下边使用
tuple_size_v
C++ template <class _Tp> _LIBCPP_INLINE_VAR constexpr size_t tuple_size_v = tuple_size<_Tp>::value; |
tuple_size
C++ template <class ..._Tp> struct _LIBCPP_TEMPLATE_VIS tuple_size<tuple<_Tp...> > : public integral_constant<size_t, sizeof...(_Tp)> { }; |
可以看的出来它是把tuple 拆解出来,通过integral_constant记录tuple组成元素的个数
integral_constant
C++ template <class _Tp, _Tp __v> struct _LIBCPP_TEMPLATE_VIS integral_constant { static _LIBCPP_CONSTEXPR const _Tp value = __v; typedef _Tp value_type; typedef integral_constant type; _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR operator value_type() const _NOEXCEPT {return value;} }; |
在回到上边的tuple_size_v<remove_reference_t<_Tuple>>,分析可以得出这个得到tuple的参数个数,接下来看下__make_tuple_indices它的源码__make_tuple_indices<tuple_size_v<remove_reference_t<_Tuple>>>
__make_tuple_indices
C++ template <size_t _Ep, size_t _Sp = 0> struct __make_tuple_indices { static_assert(_Sp <= _Ep, "__make_tuple_indices input error"); typedef __make_indices_imp<_Ep, _Sp> type; };
template <size_t _Ep, size_t _Sp> using __make_indices_imp = typename __detail::__make<_Ep - _Sp>::type::template __to_tuple_indices<_Sp>; template<> struct __make<0> { typedef __integer_sequence<size_t> type; }; template<> struct __make<1> { typedef __integer_sequence<size_t, 0> type; }; template<> struct __make<2> { typedef __integer_sequence<size_t, 0, 1> type; }; template<> struct __make<3> { typedef __integer_sequence<size_t, 0, 1, 2> type; }; template<> struct __make<4> { typedef __integer_sequence<size_t, 0, 1, 2, 3> type; }; template<> struct __make<5> { typedef __integer_sequence<size_t, 0, 1, 2, 3, 4> type; }; template<> struct __make<6> { typedef __integer_sequence<size_t, 0, 1, 2, 3, 4, 5> type; }; template<> struct __make<7> { typedef __integer_sequence<size_t, 0, 1, 2, 3, 4, 5, 6> type; }; |
上边源码我整理到一起了,方便查看,举个例子假如tuple<int, double, float>,推导过程会是template<> struct __make<3>,其type就是__integer_sequence<size_t, 0, 1, 2>,也就是__detail::__make<_Ep - _Sp>::type == __integer_sequence<size_t, 0, 1, 2>, 那typename __detail::__make<_Ep - _Sp>::type::template __to_tuple_indices<_Sp> = __integer_sequence<size_t, 0, 1, 2>::__to_tuple_indices<_Sp>, 接下来我们就看下__integer_sequence它的源码
__integer_sequence
C++ template <size_t...> struct __tuple_indices {};
template <class _IdxType, _IdxType... _Values> struct __integer_sequence { template <template <class _OIdxType, _OIdxType...> class _ToIndexSeq, class _ToIndexType> using __convert = _ToIndexSeq<_ToIndexType, _Values...>;
template <size_t _Sp> using __to_tuple_indices = __tuple_indices<(_Values + _Sp)...>; }; |
这个很简单就是把用__tuple_indices记录一个index 序列
经过上边解读,你活着好奇如果超过8个怎么半,你就会看到struct __make : __parity<_Np % 8> 推导到这个也就会它会拆分两部分,一部分是8的余数,另外一部分 如果大于8还会继续拆解,最终会把拆解后的统一合并成__tuple_indices<0,1,2,3,4,....>;
C++ template<typename _Tp, size_t ..._Extra> struct __repeat; template<typename _Tp, _Tp ..._Np, size_t ..._Extra> struct __repeat<__integer_sequence<_Tp, _Np...>, _Extra...> { typedef _LIBCPP_NODEBUG_TYPE __integer_sequence<_Tp, _Np..., sizeof...(_Np) + _Np..., 2 * sizeof...(_Np) + _Np..., 3 * sizeof...(_Np) + _Np..., 4 * sizeof...(_Np) + _Np..., 5 * sizeof...(_Np) + _Np..., 6 * sizeof...(_Np) + _Np..., 7 * sizeof...(_Np) + _Np..., _Extra...> type; }; template<size_t _Np> struct __make : __parity<_Np % 8>::template __pmake<_Np> {};
template<> struct __parity<0> { template<size_t _Np> struct __pmake : __repeat<typename __make<_Np / 8>::type> {}; }; template<> struct __parity<1> { template<size_t _Np> struct __pmake : __repeat<typename __make<_Np / 8>::type, _Np - 1> {}; }; template<> struct __parity<2> { template<size_t _Np> struct __pmake : __repeat<typename __make<_Np / 8>::type, _Np - 2, _Np - 1> {}; }; template<> struct __parity<3> { template<size_t _Np> struct __pmake : __repeat<typename __make<_Np / 8>::type, _Np - 3, _Np - 2, _Np - 1> {}; }; template<> struct __parity<4> { template<size_t _Np> struct __pmake : __repeat<typename __make<_Np / 8>::type, _Np - 4, _Np - 3, _Np - 2, _Np - 1> {}; }; template<> struct __parity<5> { template<size_t _Np> struct __pmake : __repeat<typename __make<_Np / 8>::type, _Np - 5, _Np - 4, _Np - 3, _Np - 2, _Np - 1> {}; }; template<> struct __parity<6> { template<size_t _Np> struct __pmake : __repeat<typename __make<_Np / 8>::type, _Np - 6, _Np - 5, _Np - 4, _Np - 3, _Np - 2, _Np - 1> {}; }; template<> struct __parity<7> { template<size_t _Np> struct __pmake : __repeat<typename __make<_Np / 8>::type, _Np - 7, _Np - 6, _Np - 5, _Np - 4, _Np - 3, _Np - 2, _Np - 1> {}; }; |
综上所述,可以得到
typename __make_tuple_indices<tuple_size_v<remove_reference_t<_Tuple>>>::type = __tuple_indices<0,1,2,3,4,....>,那紧接着我在看下最后的调用
__make_from_tuple_impl
C++ template <class _Tp, class _Tuple, size_t... _Idx> inline _LIBCPP_INLINE_VISIBILITY constexpr _Tp __make_from_tuple_impl(_Tuple&& __t, __tuple_indices<_Idx...>) _Tp(_VSTD::get<_Idx>(_VSTD::forward<_Tuple>(__t))... ) |
可以看的出来其本质就是调用_Tp 然后通过std::get把tuple里边的参数一个一个拿出来,做为_Tp的构造方法的参数
总结:
经过这么多其实都是编译器帮我门做的事情,最终本质就是 构造方法的调用,我门可以看下编译后的符号表是什么样的std::__1::__make_from_tuple_impl<Foo, std::__1::tuple<int, float, int>, 0ul, 1ul, 2ul>(std::__1::tuple<int, float, int>&&, std::__1::__tuple_indices<0ul, 1ul, 2ul>),大体跟我们上边介绍是一样的。
我们改进一下
Assembly language struct Foo { Foo(int first, float second, int third) { a = first; b = second; c = third; } int a; float b; int c; }; |
我们从汇编角度看下
C++ auto tuple = std::make_tuple(0x123456, 3.14f, 0); Foo fool = std::make_from_tuple<Foo>(std::move(tuple)); |
它对应的汇编std::make_from_tuple<Foo>(std::move(tuple))
C++ 0x100003658 <+16>: mov x8, #0x3456 0x10000365c <+20>: movk x8, #0x12, lsl #16 0x100003660 <+24>: movk x8, #0xf5c3, lsl #32 0x100003664 <+28>: movk x8, #0x4048, lsl #48 0x100003668 <+32>: str x8, [sp, #0x8] 0x10000366c <+36>: str wzr, [sp, #0x10] |
可以看的出来,根本没有函数调用,那为什么刚才那个构造方法家了cout就会有函数调用呢,是因为constexpr 推到的时候并不是真正的推到撑编译处理,因此我们看到汇编就可以搞清楚怎么回事了