【问题标题】:Slicing std::array切片 std::array
【发布时间】:2013-05-20 03:47:42
【问题描述】:

有没有一种简单的方法可以在 C++ 中获取数组的切片?

也就是说,我有

array<double, 10> arr10;

想要得到由arr10的前五个元素组成的数组:

array<double, 5> arr5 = arr10.???

(除了通过遍历第一个数组来填充它)

【问题讨论】:

  • 一对迭代器是 C++ 中最接近切片的。 Boost.Range 之类的东西也确实允许您将它们打包成一个类似切片的对象。

标签: c++ arrays slice


【解决方案1】:

std::array 的构造函数是隐式定义的,因此您不能使用另一个容器或迭代器的范围对其进行初始化。你能得到的最接近的方法是创建一个辅助函数,在构造过程中负责复制。这允许单阶段初始化,这是我相信您正在努力实现的目标。

template<class X, class Y>
X CopyArray(const Y& src, const size_t size)
{
    X dst;
    std::copy(src.begin(), src.begin() + size, dst.begin());
    return dst;
}
std::array<int, 5> arr5 = CopyArray<decltype(arr5)>(arr10, 5);

您还可以使用 std::copy 之类的内容或自己遍历副本。

std::copy(arr10.begin(), arr10.begin() + 5, arr5.begin());

【讨论】:

    【解决方案2】:

    当然。写了这个:

    template<int...> struct seq {};
    template<typename seq> struct seq_len;
    template<int s0,int...s>
    struct seq_len<seq<s0,s...>>:
        std::integral_constant<std::size_t,seq_len<seq<s...>>::value> {};
    template<>
    struct seq_len<seq<>>:std::integral_constant<std::size_t,0> {};
    template<int Min, int Max, int... s>
    struct make_seq: make_seq<Min, Max-1, Max-1, s...> {};
    template<int Min, int... s>
    struct make_seq<Min, Min, s...> {
      typedef seq<s...> type;
    };
    template<int Max, int Min=0>
    using MakeSeq = typename make_seq<Min,Max>::type;
    
    template<std::size_t src, typename T, int... indexes>
    std::array<T, sizeof...(indexes)> get_elements( seq<indexes...>, std::array<T, src > const& inp ) {
      return { inp[indexes]...  };
    }
    template<int len, typename T, std::size_t src>
    auto first_elements( std::array<T, src > const& inp )
      -> decltype( get_elements( MakeSeq<len>{}, inp ) )
    {
      return get_elements( MakeSeq<len>{}, inp  );
    }
    

    编译时indexes... 进行重新映射,MakeSeq 生成从 0 到 n-1 的序列。

    Live example.

    这支持任意一组索引(通过get_elements)和第一个n(通过first_elements)。

    用途:

    std::array< int, 10 > arr = {0,1,2,3,4,5,6,7,8,9};
    std::array< int, 6 > slice = get_elements(arr, seq<2,0,7,3,1,0>() );
    std::array< int, 5 > start = first_elements<5>(arr);
    

    避免所有循环,无论是显式的还是隐式的。

    【讨论】:

    • 哇,这很快 :-) 我再次陷入了 enter != linebreak 陷阱。现在可以使用 Visual Studio 2017,非常感谢!
    【解决方案3】:

    2018 更新,如果你只需要first_elements:

    使用 C++14 的更少样板解决方案(基于 Yakk's pre-14 answer 并从 "unpacking" a tuple to call a matching function pointer 窃取)

    template < std::size_t src, typename T, int... I >
    std::array< T, sizeof...(I) > get_elements(std::index_sequence< I... >, std::array< T, src > const& inp)
    {
        return { inp[I]... };
    }
    template < int N, typename T, std::size_t src >
    auto first_elements(std::array<T, src > const& inp)
        -> decltype(get_elements(std::make_index_sequence<N>{}, inp))
    {
        return get_elements(std::make_index_sequence<N>{}, inp);
    }
    

    仍然无法解释为什么这有效,但它确实有效(在 Visual Studio 2017 上对我而言)。

    【讨论】:

      【解决方案4】:

      这个答案可能会迟到......但我只是在玩切片 - 所以这是我的 std::array 切片的小家酿。

      当然,这有一些限制,最终并不普遍:

      • 从中获取切片的源数组不得超出范围。我们存储对源的引用。
      • 我首先在寻找常量数组切片,并没有尝试将此代码扩展到 const 和非 const 切片。

      但下面代码的一个不错的功能是,您可以切片切片...

      // ParCompDevConsole.cpp : This file contains the 'main' function. Program execution begins and ends there.
      //
      
      #include "pch.h"
      #include <cstdint>
      #include <iostream>
      #include <array>
      #include <stdexcept>
      #include <sstream>
      #include <functional>
      
          template <class A>
          class ArraySliceC
          {
          public:
              using Array_t = A;
              using value_type = typename A::value_type;
              using const_iterator = typename A::const_iterator;
      
              ArraySliceC(const Array_t & source, size_t ifirst, size_t length)
                  : m_ifirst{ ifirst }
                  , m_length{ length }
                  , m_source{ source }
              {
                  if (source.size() < (ifirst + length))
                  {
                      std::ostringstream os;
                      os << "ArraySliceC::ArraySliceC(<source>,"
                          << ifirst << "," << length
                          << "): out of bounds. (ifirst + length >= <source>.size())";
                      throw std::invalid_argument( os.str() );
                  }
              }
              size_t size() const 
              { 
                  return m_length; 
              }
              const value_type& at( size_t index ) const 
              { 
                  return m_source.at( m_ifirst + index ); 
              }
              const value_type& operator[]( size_t index ) const
              {
                  return m_source[m_ifirst + index];
              }
              const_iterator cbegin() const
              {
                  return m_source.cbegin() + m_ifirst;
              }
              const_iterator cend() const
              {
                  return m_source.cbegin() + m_ifirst + m_length;
              }
      
          private:
              size_t m_ifirst;
              size_t m_length;
              const Array_t& m_source;
          };
      
      template <class T, size_t SZ>
      std::ostream& operator<<( std::ostream& os, const std::array<T,SZ>& arr )
      {
          if (arr.size() == 0)
          {
              os << "[||]";
          }
          else
          {
              os << "[| " << arr.at( 0 );
              for (auto it = arr.cbegin() + 1; it != arr.cend(); it++)
              {
                  os << "," << (*it);
              }
              os << " |]";
          }
          return os;
      }
      
      template<class A>
      std::ostream& operator<<( std::ostream& os, const ArraySliceC<A> & slice )
      {
          if (slice.size() == 0)
          {
              os <<  "^[||]";
          }
          else
          {
              os << "^[| " << slice.at( 0 );
              for (auto it = slice.cbegin() + 1; it != slice.cend(); it++)
              {
                  os << "," << (*it);
              }
              os << " |]";
          }
          return os;
      }
      
      template<class A>
      A unfoldArray( std::function< typename A::value_type( size_t )> producer )
      {
          A result;
          for (size_t i = 0; i < result.size(); i++)
          {
              result[i] = producer( i );
          }
          return result;
      }
      
      int main()
      {
          using A = std::array<float, 10>;
          auto idf = []( size_t i ) -> float { return static_cast<float>(i); };
          const auto values = unfoldArray<A>(idf);
      
          std::cout << "values = " << values << std::endl;
      
          // zero copy slice of values array.
          auto sl0 = ArraySliceC( values, 2, 4 );
          std::cout << "sl0 = " << sl0 << std::endl;
      
          // zero copy slice of the sl0 (the slice of values array)
          auto sl01 = ArraySliceC( sl0, 1, 2 );
          std::cout << "sl01 = " << sl01 << std::endl;
      
          return 0;
      }
      

      【讨论】:

      • 这看起来很糟糕(阅读:低效且不灵活)C++20 / GSL span... 除了允许不连续的序列。
      • 现在你提到了 c++ 的人称它为span,我找到了。他们应该将其命名为slice,我本可以为自己节省这项工作。但是……@Deduplicator……这个实现有什么低效的地方?
      • 你有三个成员:对容器的引用、切片的开始、切片的长度。但是对于随机访问容器,您只需要开始迭代器和长度或结束迭代器。此外,您应该使用noexceptconstexpr
      • 但是我的容器是随机访问的(当然是只读的)。而且,乍一看,我认为std::span 也是一个品味问题......他们需要subspan 函数,而我的切片可以作为子函数的数组替换。它适用于数组和切片源。而且我也没有在我的实现中“去指针”。最后同样重要的是 - 通过 1 个额外的功能,我可以检索源代码。这是迭代器通常不提供 AFAIK 的一件事。
      • 你知道,对容器的无知,除了足以找到序列中的所有元素之外,是一个特性而不是一个错误。它大大提高了灵活性,无需额外费用。
      猜你喜欢
      • 1970-01-01
      • 2019-01-31
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-12-15
      • 2023-02-11
      • 1970-01-01
      • 2020-02-07
      相关资源
      最近更新 更多