std::make_from_tuple 源码分析

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

C++
42, 3.14, 0

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 推到的时候并不是真正的推到撑编译处理,因此我们看到汇编就可以搞清楚怎么回事了

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值