【问题标题】:How to split a tuple?如何拆分元组?
【发布时间】:2012-05-16 21:16:46
【问题描述】:

给定一个

   template<typename First, typename... Tail>
   struct something
   {
       std::tuple<First, Tail...> t;
   };

我怎样才能得到一个包含t 中除第一个元素之外的所有元素的std::tuple&lt;Tail...&gt;


我认为总的来说这是一个有趣的问题,但这是我的上下文动机:

我想为元组实现哈希。我以this answer 为基础。我发现里面有一个错误,即没有用值调用hash对象的operator()

return left() ^ right();

应该是:

return left(std::get<0>(e)) ^ right(???);

???将是元组的剩余元素以继续模板的递归实例化。这是包含终止部分的完整代码:

#include <functional>
#include <utility>

namespace std
{

template<typename First, typename... Tail>
struct hash<std::tuple<First, Tail...>>
{
    typedef size_t result_type;
    typedef std::tuple<First, Tail...> argument_type;

    result_type operator()(argument_type const& e) const
    {
        std::hash<First> left;
        std::hash<std::tuple<Tail...>> right;
        return left(std::get<0>(e)) ^ right(???);
    }
};

template<>
struct hash<std::tuple<>>
{
    typedef size_t result_type;
    typedef std::tuple<> argument_type;

    result_type operator()(argument_type const& e) const
    {
        return 1;
    }
};

}

【问题讨论】:

    标签: c++ c++11


    【解决方案1】:

    我正在寻找同样的东西,并想出了这个相当直接的 C++14 解决方案:

    #include <iostream>
    #include <tuple>
    #include <utility>
    
    template < typename T , typename... Ts >
    auto head( std::tuple<T,Ts...> t )
    {
       return  std::get<0>(t);
    }
    
    template < std::size_t... Ns , typename... Ts >
    auto tail_impl( std::index_sequence<Ns...> , std::tuple<Ts...> t )
    {
       return  std::make_tuple( std::get<Ns+1u>(t)... );
    }
    
    template < typename... Ts >
    auto tail( std::tuple<Ts...> t )
    {
       return  tail_impl( std::make_index_sequence<sizeof...(Ts) - 1u>() , t );
    }
    
    int main()
    {
       auto t = std::make_tuple( 2, 3.14 , 'c' );
       std::cout << head(t) << std::endl;
       std::cout << std::get<0>( tail(t) ) << std::endl;
       std::cout << std::get<1>( tail(t) ) << std::endl;
    }
    

    因此,head(.) 返回元组的第一个元素,tail(.) 返回仅包含最后 N-1 个元素的新元组。

    【讨论】:

      【解决方案2】:

      使用“索引元组”解包元组,无需递归:

      #include <redi/index_tuple.h>
      
      template<typename T, typename... U, unsigned... I>
        inline std::tuple<U...>
        cdr_impl(const std::tuple<T, U...>& t, redi::index_tuple<I...>)
        { return std::tuple<U...>{ std::get<I+1>(t)... }; }
      
      template<typename T, typename... U>
        inline std::tuple<U...>
        cdr(const std::tuple<T, U...>& t)
        { return cdr_impl(t, redi::to_index_tuple<U...>()); }
      

      请参阅https://gitlab.com/redistd/redistd/blob/master/include/redi/index_tuple.h 了解make_index_tupleindex_tuple,它们是恕我直言处理元组和类似可变参数类模板的基本实用程序。 (类似的实用程序在 C++14 中被标准化为 std::index_sequence,请参阅 index_seq.h 了解独立的 C++11 实现)。

      或者,一个非复制版本使用std::tie 获取尾部的references 元组,而不是复制每个元素:

      #include <redi/index_tuple.h>
      
      template<typename T, typename... U, unsigned... I>
        inline std::tuple<const U&...>
        cdr_impl(const std::tuple<T, U...>& t, redi::index_tuple<I...>)
        { return std::tie( std::get<I+1>(t)... ); }
      
      template<typename T, typename... U>
        inline std::tuple<const U&...>
        cdr(const std::tuple<T, U...>& t)
        { return cdr_impl(t, redi::to_index_tuple<U...>()); }
      

      【讨论】:

      • 您可以将索引包与redi::index_tuple&lt;0, I...&gt; 匹配,I 将从索引1 开始,即我们关心的那些索引。
      • 您仍在使用递归,它只是在您正在使用的 index_tuple.h 实用程序中。
      • @Luc Danton,好主意,但您也需要进行其他更改,否则参数包的大小将与 U 不同
      • @Crazy Eddie,是的,但这是您编写一次(或复制)并重复使用的实用程序,而不是每次对元组执行任何操作时都必须重新实现递归解决方案。
      • 基于index_tuple 的类型将在C++14 :)
      【解决方案3】:

      这是我可以首先尝试的。可能会更好:

      #include <tuple>
      
      template < typename Target, typename Tuple, int N, bool end >
      struct builder
      {
          template < typename ... Args >
          static Target create(Tuple const& t, Args && ... args)
          {
              return builder<Target,Tuple, N+1, std::tuple_size<Tuple>::value == N+1>::create(t, std::forward<Args>(args)..., std::get<N>(t));
          }
      };
      
      template < typename Target, typename Tuple, int N >
      struct builder<Target,Tuple,N,true>
      {
          template < typename ... Args >
          static Target create(Tuple const& t, Args && ... args) { return Target(std::forward<Args>(args)...); }
      };
      
      template < typename Head, typename ... Tail >
      std::tuple<Tail...> cdr(std::tuple<Head,Tail...> const& tpl)
      {
          return builder<std::tuple<Tail...>, std::tuple<Head,Tail...>, 1, std::tuple_size<std::tuple<Head,Tail...>>::value == 1>::create(tpl);
      }
      
      #include <iostream>
      int main() {
          std::tuple<int,char,double> t1(42,'e',16.7);
          std::tuple<char,double> t2 = cdr(t1);
      
          std::cout << std::get<0>(t2) << std::endl;
      }
      

      需要注意的一点是,如果您使用自己的类型而不是 std::tuple,您的情况可能会好很多。这很难的原因是标准似乎没有指定这个元组是如何工作的,因为它没有从自身继承。 boost 版本使用了一个你可以挖掘的缺点。这里有一些可能更符合你想要的东西,它会使上述所有操作变得像演员一样简单:

      template < typename ... Args > struct my_tuple;
      
      template < typename Head, typename ... Tail >
      struct my_tuple<Head,Tail...> : my_tuple<Tail...>
      {
          Head val;
          template < typename T, typename ... Args >
          my_tuple(T && t, Args && ... args) 
              : my_tuple<Tail...>(std::forward<Args>(args)...)
              , val(std::forward<T>(t)) 
          {}
      };
      
      template < >
      struct my_tuple <>
      {
      };
      

      这是未经测试的,但它应该足以说明这一点,直到它起作用为止。现在要获取“tail”类型的对象,您只需执行以下操作:

      template < typename Head, typename ... Tail >
      my_tuple<Tail...> cdr(my_tuple<Head,Tail...> const& mtpl) { return mtpl; }
      

      【讨论】:

        【解决方案4】:

        Crazy Eddie 找到了一种解包元组的方法,它确实回答了这个问题。但是,对于您提出的特定问题(即元组散列),为什么不避免所有元组副本,而是使用模板递归来依次散列每个元素?

        #include <utility>
        #include <iostream>
        
        template< typename T >
        size_t left( T const & ) {
          return 1;
        }
        
        template< int N, typename Head, typename... Tail >
        struct _hash {
          typedef size_t result_type;
          typedef std::tuple< Head, Tail... > argument_type;
        
          result_type operator ()( argument_type const &e ) const {
            return left(std::get<N>(e)) ^ _hash<N-1, Head, Tail... >()(e);
          }
        }; // end struct _hash
        
        template< typename Head, typename... Tail >
        struct _hash< 0, Head, Tail... > {
          typedef size_t result_type;
          typedef std::tuple< Head, Tail... > argument_type;
        
          result_type operator ()( argument_type const &e ) const {
            return left(std::get<0>(e));
          }
        }; // end struct _hash< 0 >
        
        template< typename Head, typename... Tail >
        size_t hash( std::tuple< Head, Tail... > const &e ) {
          return _hash< sizeof...(Tail), Head, Tail... >()( e );
        }
        
        int main( ) {
          std::tuple< int > l_tuple( 5 );
          std::cout << hash( l_tuple ) << std::endl;
        }
        

        这以相反的顺序进行散列,但异或是可交换的,所以没关系。

        【讨论】:

        • 不应该是return _hash&lt; sizeof...(Tail) - 1, Head, Tail... &gt;()( e );吗?否则,第一个递归深度中的 std::get 将失败。
        • 我不这么认为。尾部的大小比元组中的元素数小一,所以 sizeof...(Tail) 应该是尾部最后一个元素的索引。换句话说,[0, sizeof...(Tail)] 范围内的索引在这里应该是有效的。
        【解决方案5】:

        类似这样的:

        #include <tuple>
        
        template <bool, typename T, unsigned int ...N> struct tail_impl;
        
        template <typename T, typename ...Args, unsigned int ...N>
        struct tail_impl<false, std::tuple<T, Args...>, N...>
        {
            static std::tuple<Args...> go(std::tuple<T, Args...> const & x)
            {
                return tail_impl<sizeof...(N) + 1 == sizeof...(Args), std::tuple<T, Args...>, N..., sizeof...(N)>::go(x);
            }
        };
        
        template <typename T, typename ...Args, unsigned int ...N>
        struct tail_impl<true, std::tuple<T, Args...>, N...>
        {
            static std::tuple<Args...> go(std::tuple<T, Args...> const & x)
            {
                return std::tuple<Args...>(std::get<N>(x)...);
            }
        };
        
        template <typename T, typename ...Args>
        std::tuple<Args...> tail(std::tuple<T, Args...> const & x)
        {
            return tail_impl<sizeof...(Args) == 1, std::tuple<T, Args...>, 0>::go(x);
        }
        

        测试:

        #include <demangle.hpp>
        #include <iostream>
        
        typedef std::tuple<int, char, bool> TType;
        
        int main()
        {
            std::cout << demangle<TType>() << std::endl;
            std::cout << demangle<decltype(tail(std::declval<TType>()))>() << std::endl;
        }
        

        打印:

        std::tuple<int, char, bool>
        std::tuple<char, bool>
        

        【讨论】:

        • 我的测试:int main() { std::tuple&lt;int,char,double&gt; t1(42,'e',16.7); std::tuple&lt;char,double&gt; t2 = tail(t1); std::cout &lt;&lt; std::get&lt;0&gt;(t2) &lt;&lt; std::endl; } 编译失败:error: cannot call member function 'std::tuple&lt;Args ...&gt; tail_impl&lt;false, std::tuple&lt;T, Args ...&gt;, N ...&gt;::go(const std::tuple&lt;T, Args ...&gt;&amp;) [with T = int, Args = {char, double}, unsigned int ...N = {0u}]' without object
        • 抱歉,出现了一些错误。现已修复。
        【解决方案6】:

        使用 kgadek 对 get part of std::tuple 的回答和 Andre Bergner 的测试代码。这个很好很简单,但我不知道它是否便携。

        // works using gcc 4.6.3
        // g++ -std=c++0x -W -Wall -g main.cc -o main
        #include <iostream>
        #include <tuple>
        
        template < typename T , typename... Ts >
        const T& head(std::tuple<T,Ts...> t)
        {
           return  std::get<0>(t);
        }
        
        template <typename T, typename... Ts>
        const std::tuple<Ts...>& tail(const std::tuple<T, Ts...>& t)
        {
           return (const std::tuple<Ts...>&)t;
        }
        
        int main()
        {
           auto t = std::make_tuple( 2, 3.14 , 'c' );
           std::cout << head(t) << std::endl;
           std::cout << std::get<0>( tail(t) ) << std::endl;
           std::cout << std::get<1>( tail(t) ) << std::endl;
        }
        

        【讨论】:

        • 它是 UB,因为 std::tuple&lt;T, Ts...&gt;std::tuple&lt;Ts...&gt; 是不相关的类型(即使确实一些旧的实现使用了继承,但这是一个实现细节,所以不可移植)。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2020-04-05
        • 2019-10-20
        • 1970-01-01
        • 1970-01-01
        • 2018-09-20
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多