std::tuple,奇妙的模板递归

Published: April 29 2014

总算磨磨蹭蹭的看完了挪威的森林。。。。

上次既然提到了 tuple,就顺手。。。。 tuple ​ stdlibc++v3, include/std/tuple

  /// Primary class template, tuple
  template<typename... _Elements>
    class tuple : public _Tuple_impl<0, _Elements...>
    {
      typedef _Tuple_impl<0, _Elements...> _Inherited;
    public:
      constexpr tuple()
      : _Inherited() { }
      explicit
      constexpr tuple(const _Elements&... __elements)
      : _Inherited(__elements...) { }
      template<typename... _UElements, typename = typename
        enable_if<__and_<is_convertible<_UElements,
                                        _Elements>...>::value>::type>
        explicit
        constexpr tuple(_UElements&&... __elements)
        : _Inherited(std::forward<_UElements>(__elements)...) { }
      constexpr tuple(const tuple&) = default;
      constexpr tuple(tuple&&) = default;

      template<typename... _UElements, typename = typename
        enable_if<__and_<is_convertible<const _UElements&,
                                        _Elements>...>::value>::type>
        constexpr tuple(const tuple<_UElements...>& __in)
        : _Inherited(static_cast<const _Tuple_impl<0, _UElements...>&>(__in))
        { }
      template<typename... _UElements, typename = typename
        enable_if<__and_<is_convertible<_UElements,
                                        _Elements>...>::value>::type>
        constexpr tuple(tuple<_UElements...>&& __in)
        : _Inherited(static_cast<_Tuple_impl<0, _UElements...>&&>(__in)) { }

class tuple 本身并没有很大信息量,提供构造函数,conversion 等。连个 member 都没有,关键部分都在 _Tuple_Impl 里面。

到下面,看到了一些奇奇怪怪的东西。

      template<typename _Alloc>
        tuple(allocator_arg_t __tag, const _Alloc& __a)
        : _Inherited(__tag, __a) { }

我们暂时先不理 allocator_arg 这些东西,跟主线剧情无关。

后面就是 operator=(&) 和 operator(&&),同类型和不同类型。

      tuple&
      operator=(const tuple& __in)
      {
        static_cast<_Inherited&>(*this) = __in;
        return *this;
      }
      tuple&
      operator=(tuple&& __in)
      noexcept(is_nothrow_move_assignable<_Inherited>::value)
      {
        static_cast<_Inherited&>(*this) = std::move(__in);
        return *this;
      }
      template<typename... _UElements, typename = typename
               enable_if<sizeof...(_UElements)
                         == sizeof...(_Elements)>::type>
        tuple&
        operator=(const tuple<_UElements...>& __in)
        {
          static_cast<_Inherited&>(*this) = __in;
          return *this;
        }
      template<typename... _UElements, typename = typename
               enable_if<sizeof...(_UElements)
                         == sizeof...(_Elements)>::type>
        tuple&
        operator=(tuple<_UElements...>&& __in)
        {
          static_cast<_Inherited&>(*this) = std::move(__in);
          return *this;
        }

注意,这里 enable_if 只要两边的 sizeof(types) 相同就可以。。。难道我们可以做一些奇怪的事情?比如说

    tuple<我不是int但是跟int大小一样, double> c;
    c = tuple<int, double>(1, 2.0);

不过显然是想多了,还有 base 的 operator=(&&) 挡着呢。

后面的 swap 调用了 base 的 swap。

      void
      swap(tuple& __in)
      noexcept(noexcept(__in._M_swap(__in)))
      { _Inherited::_M_swap(__in); }

来看 _Tuple_Impl

  template<std::size_t _Idx, typename... _Elements>
    struct _Tuple_impl;

  template<std::size_t _Idx>
    struct _Tuple_impl<_Idx>

  template<std::size_t _Idx, typename _Head, typename... _Tail>
    struct _Tuple_impl<_Idx, _Head, _Tail...>
    : public _Tuple_impl<_Idx + 1, _Tail...>,
      private _Head_base<_Idx, _Head, __empty_not_final<_Head>::value>

是在用模板做递归继承的勾当,而 _Head_base 就是当前的 type 储存的地方。tuple 是继承 _Idx == 0 时的版本

class tuple : public _Tuple_impl<0, _Elements...>

我们来理一下递推关系吧。假设有这样一个 tuple<int, char>,那么 tuple : _Tuple_impl<0, int, char, bool>。 _Tuple_impl 发生模板匹配的时候,_Head 匹配到了 int,而 typename… _Tail 匹配到了 char, bool (可变模板嘛)。而根据继承的递推关系,我们有 _Tuple_impl<0, int, ….> : _Tuple_impl<1, ….. > 。省略号的部分继续匹配 _Tuple_impl,此时 char 被 _Head 匹配,而 _Tail 中只剩 bool 一个。。。匹配到最后,Tail 中已经没有类型了,我们到了 _Tuple_Impl<_Idx> ,也就是整个递推的 base,结束。

而这些 int, char, bool 则都交给了 _Head_base 来管理,我们来看一下。

  template<std::size_t _Idx, typename _Head, bool _IsEmptyNotFinal>
    struct _Head_base;
  template<std::size_t _Idx, typename _Head>
    struct _Head_base<_Idx, _Head, true>
    : public _Head

  template<std::size_t _Idx, typename _Head>
    struct _Head_base<_Idx, _Head, false>

在做跟 compressed_pair 类似的勾当,还是想尽量做空类继承优化。并没有太多细节,略过一切 alloc 有关的东西。

回到 _tuple_impl,我们顺手看一下 swap

    protected:
      void
      _M_swap(_Tuple_impl& __in)
      noexcept(noexcept(swap(std::declval<_Head&>(),
                             std::declval<_Head&>()))
               && noexcept(_M_tail(__in)._M_swap(_M_tail(__in))))
      {
        using std::swap;
        swap(_M_head(*this), _M_head(__in));
        _Inherited::_M_swap(_M_tail(__in));
      }

也不过如此,分别交换 head 和 base。

然后 _Tuple_impl 里面就是各种炫酷的 operator=(&&) operator=(&) 了,就不细说了。

关于 tuple,当然后面还有内容。

  template<>
    class tuple<>
    {
    public:
      void swap(tuple&) noexcept { /* no-op */ }
    };

对空 tuple 的显示特化。(库里面总是会做一些你想不到的奇怪事情)

  template<typename _T1, typename _T2>
    class tuple<_T1, _T2> : public _Tuple_impl<0, _T1, _T2>

      template<typename _U1, typename _U2, typename = typename
        enable_if<__and_<is_convertible<const _U1&, _T1>,
                         is_convertible<const _U2&, _T2>>::value>::type>
        constexpr tuple(const pair<_U1, _U2>& __in)
        : _Inherited(__in.first, __in.second) { }
      template<typename _U1, typename _U2, typename = typename
               enable_if<__and_<is_convertible<_U1, _T1>,
                                is_convertible<_U2, _T2>>::value>::type>
        constexpr tuple(pair<_U1, _U2>&& __in)
        : _Inherited(std::forward<_U1>(__in.first),
                     std::forward<_U2>(__in.second)) { }

对两个元素的 tuple 提供从 pair 构造的方法。

对于 tuple 本身,基本完结了。不过 tuple 周边还有东西哟。

std::get 拿到 tuple 中的元素,std::tuple_element 拿到 tuple 中的类型,tuple_size 等等。 想一想,其实这些东西原理应该都差不多,搞的定一个,其他的基本同理了。随便 yy 一下,直接跟 tuple 构造一样做模板递归不就好了~

我们来看最喜闻乐见的 std::get 先。

显示 get<size_t>

  template<std::size_t __i, typename _Head, typename... _Tail>
    constexpr typename __add_ref<_Head>::type
    __get_helper(_Tuple_impl<__i, _Head, _Tail...>& __t) noexcept
    { return _Tuple_impl<__i, _Head, _Tail...>::_M_head(__t); }
  template<std::size_t __i, typename _Head, typename... _Tail>
    constexpr typename __add_c_ref<_Head>::type
    __get_helper(const _Tuple_impl<__i, _Head, _Tail...>& __t) noexcept
    { return _Tuple_impl<__i, _Head, _Tail...>::_M_head(__t); }

  // Return a reference (const reference, rvalue reference) to the ith element
  // of a tuple. Any const or non-const ref elements are returned with their
  // original type.
  template<std::size_t __i, typename... _Elements>
    constexpr typename __add_ref<
                      typename tuple_element<__i, tuple<_Elements...>>::type
                    >::type
    get(tuple<_Elements...>& __t) noexcept
    { return std::__get_helper<__i>(__t); }
  template<std::size_t __i, typename... _Elements>
    constexpr typename __add_c_ref<
                      typename tuple_element<__i, tuple<_Elements...>>::type
                    >::type
    get(const tuple<_Elements...>& __t) noexcept
    { return std::__get_helper<__i>(__t); }
  template<std::size_t __i, typename... _Elements>
    constexpr typename __add_r_ref<
                      typename tuple_element<__i, tuple<_Elements...>>::type
                    >::type
    get(tuple<_Elements...>&& __t) noexcept
    { return std::forward<typename tuple_element<__i,
        tuple<_Elements...>>::type&&>(get<__i>(__t)); }

然后是 get<type>

#if __cplusplus > 201103L
  template<typename _Head, size_t __i, typename... _Tail>
    constexpr typename __add_ref<_Head>::type
    __get_helper2(_Tuple_impl<__i, _Head, _Tail...>& __t) noexcept
    { return _Tuple_impl<__i, _Head, _Tail...>::_M_head(__t); }
  template<typename _Head, size_t __i, typename... _Tail>
    constexpr typename __add_c_ref<_Head>::type
    __get_helper2(const _Tuple_impl<__i, _Head, _Tail...>& __t) noexcept
    { return _Tuple_impl<__i, _Head, _Tail...>::_M_head(__t); }
  template <typename _Tp, typename... _Types>
    constexpr _Tp&
    get(tuple<_Types...>& __t) noexcept
    { return std::__get_helper2<_Tp>(__t); }
  template <typename _Tp, typename... _Types>
    constexpr _Tp&&
    get(tuple<_Types...>&& __t) noexcept
    { return std::move(std::__get_helper2<_Tp>(__t)); }
  template <typename _Tp, typename... _Types>
    constexpr const _Tp&
    get(const tuple<_Types...>& __t) noexcept
    { return std::__get_helper2<_Tp>(__t); }
#endif

get<type> 的时候如果你一个 tuple 里面有两个 这个 type 的时候,就会出现两个可以匹配的 _Tuple_Impl,编译挂掉。

并不复杂,直接从 _Tuple_impl::_M_head 拿了。

      typedef _Head_base<_Idx, _Head, __empty_not_final<_Head>::value> _Base;

      static constexpr _Head&
      _M_head(_Tuple_impl& __t) noexcept { return _Base::_M_head(__t); }
      static constexpr const _Head&
      _M_head(const _Tuple_impl& __t) noexcept { return _Base::_M_head(__t); }

下面还有好玩的各种比较 operator

  template<typename... _TElements, typename... _UElements>
    constexpr bool
    operator!=(const tuple<_TElements...>& __t,
               const tuple<_UElements...>& __u)
    { return !(__t == __u); }
  template<typename... _TElements, typename... _UElements>
    constexpr bool
    operator>(const tuple<_TElements...>& __t,
              const tuple<_UElements...>& __u)
    { return __u < __t; }
  template<typename... _TElements, typename... _UElements>
    constexpr bool
    operator<=(const tuple<_TElements...>& __t,
               const tuple<_UElements...>& __u)
    { return !(__u < __t); }
  template<typename... _TElements, typename... _UElements>
    constexpr bool
    operator>=(const tuple<_TElements...>& __t,
               const tuple<_UElements...>& __u)
    { return !(__t < __u); }

都是基于 operator< 和 operator= 定义的。

  template<typename... _TElements, typename... _UElements>
    constexpr bool
    operator==(const tuple<_TElements...>& __t,
               const tuple<_UElements...>& __u)
    {
      typedef tuple<_TElements...> _Tp;
      typedef tuple<_UElements...> _Up;
      return bool(__tuple_compare<tuple_size<_Tp>::value - tuple_size<_Up>::value,
              0, tuple_size<_Tp>::value, _Tp, _Up>::__eq(__t, __u));
    }
  template<typename... _TElements, typename... _UElements>
    constexpr bool
    operator<(const tuple<_TElements...>& __t,
              const tuple<_UElements...>& __u)
    {
      typedef tuple<_TElements...> _Tp;
      typedef tuple<_UElements...> _Up;
      return bool(__tuple_compare<tuple_size<_Tp>::value - tuple_size<_Up>::value,
              0, tuple_size<_Tp>::value, _Tp, _Up>::__less(__t, __u));
    }

operator== 和 operator< 借助 __tuple_compare 来做比较。

  // This class helps construct the various comparison operations on tuples
  template<std::size_t __check_equal_size, std::size_t __i, std::size_t __j,
           typename _Tp, typename _Up>
    struct __tuple_compare;
  template<std::size_t __i, std::size_t __j, typename _Tp, typename _Up>
    struct __tuple_compare<0, __i, __j, _Tp, _Up>
    {
      static constexpr bool
      __eq(const _Tp& __t, const _Up& __u)
      {
        return (get<__i>(__t) == get<__i>(__u) &&
                __tuple_compare<0, __i + 1, __j, _Tp, _Up>::__eq(__t, __u));
      }
      static constexpr bool
      __less(const _Tp& __t, const _Up& __u)
      {
        return ((get<__i>(__t) < get<__i>(__u))
                || !(get<__i>(__u) < get<__i>(__t)) &&
                __tuple_compare<0, __i + 1, __j, _Tp, _Up>::__less(__t, __u));
      }
    };
  template<std::size_t __i, typename _Tp, typename _Up>
    struct __tuple_compare<0, __i, __i, _Tp, _Up>
    {
      static constexpr bool
      __eq(const _Tp&, const _Up&) { return true; }
      static constexpr bool
      __less(const _Tp&, const _Up&) { return false; }
    };

__tuple_compare 先通过 __check_equal_size 进行判断,两个 tuple 是不是有同样多的元素。__tuple_compare 只对 __check_equal_size == 0 的情况作了特化,不一样就会挂。 [b]而 __i 作为从 0 开始的步进量, __j 则是 tupe_size,__i 从 0 到 tuple_size 开始,逐个比较 tuple 中的元素。而如果 __i 到了 __j ,就到了 __tuple_compare 第二个特化中去了~~ 一看便知。相当于编译期生成 loop 喔。[/b]

顺便路过 make_tuple 和 forward_as_tuple

  template<typename... _Elements>
    constexpr tuple<typename __decay_and_strip<_Elements>::__type...>
    make_tuple(_Elements&&... __args)
    {
      typedef tuple<typename __decay_and_strip<_Elements>::__type...>
        __result_type;
      return __result_type(std::forward<_Elements>(__args)...);
    }
  template<typename... _Elements>
    tuple<_Elements&&...>
    forward_as_tuple(_Elements&&... __args) noexcept
    { return tuple<_Elements&&...>(std::forward<_Elements>(__args)...); }

对了,我比较关心的还有 std::tie 和 std::ignore.

  /// tie
  template<typename... _Elements>
    inline tuple<_Elements&...>
    tie(_Elements&... __args) noexcept
    { return tuple<_Elements&...>(__args...); }

哈哈,就是引用,会不会有些失望。

  // A class (and instance) which can be used in 'tie' when an element
  // of a tuple is not required
  struct _Swallow_assign
  {
    template<class _Tp>
      const _Swallow_assign&
      operator=(const _Tp&) const
      { return *this; }
  };
  const _Swallow_assign ignore{};

这就是 ignore !! 恍然大悟。真是。。。给跪下了啊

总结一下~~

  1. 编译期模板的递归真是强大啊,可以生成递归类型,还有递归代码逻辑(operator 比较那个)
  2. 另外库里面各种 forward && noexcept 应该找时间专门研究一下~~~ 还有让人蛋碎的引用塌陷折叠
> 本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。 <
blog comments powered by Disqus