11

Is there an easy way to get a slice of an array in C++?

I.e., I've got

array<double, 10> arr10;

and want to get array consisting of five first elements of arr10:

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

(other than populating it by iterating through first array)

sashkello
  • 17,306
  • 24
  • 81
  • 109
  • 1
    A pair of iterators is the closest C++ has to a slice. Something like Boost.Range does allow you to pack them together into one slice-like object, too. – Luc Danton May 20 '13 at 06:44

4 Answers4

9

The constructors for std::array are implicitly defined so you can't initialize it with a another container or a range from iterators. The closest you can get is to create a helper function that takes care of the copying during construction. This allows for single phase initialization which is what I believe you're trying to achieve.

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);

You can also use something like std::copy or iterate through the copy yourself.

std::copy(arr10.begin(), arr10.begin() + 5, arr5.begin());
boblicious
  • 3,901
  • 1
  • 25
  • 22
Captain Obvlious
  • 19,754
  • 5
  • 44
  • 74
8

Sure. Wrote this:

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  );
}

Where the compile time indexes... does the remapping, and MakeSeq makes a seq from 0 to n-1.

Live example.

Updated to more recent versions of C++:

template<class Seq, std::size_t N>
struct add_to_seq;
template<class Seq, std::size_t N>
using add_to_seq_t = typename add_to_seq<Seq, N>::type;

template<std::size_t...Is, std::size_t N>
struct add_to_seq<std::index_sequence<Is...>, N>
{
    using type=std::index_sequence<(Is+N)...>;
};

template<std::size_t Min, std::size_t Max>
struct make_seq {
    using type=add_to_seq_t< std::make_index_sequence<Max-Min>, Min >;
};
template<std::size_t Min, std::size_t Max>
using make_seq_t = typename make_seq<Min,Max>::type;

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

Live example.

(The more modern version generates O(n) total compile time template types - the older version was O(n^2) in compile time template generation. This means a faster compile and less likely to hit compiler resource limits.)

This supports both an arbitrary set of indexes (via get_elements) and the first n (via first_elements).

Use:

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);

which avoids all loops, either explicit or implicit.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • Wow, that was quick :-) I fell for the enter != linebreak trap again. Works like a charm with Visual Studio 2017 now, thanks a bunch! – dlw Jul 10 '18 at 15:37
1

2018 update, if all you need is first_elements:

Less boilerplaty solution using C++14 (building up on Yakk's pre-14 answer and stealing from "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);
}

Still cannot explain why this works, but it does (for me on Visual Studio 2017).

dlw
  • 323
  • 2
  • 14
1

This answer might be late... but I was just toying around with slices - so here is my little home brew of std::array slices.

Of course, this comes with a few restrictions and is not ultimately general:

  • The source array from which a slice is taken must not go out of scope. We store a reference to the source.
  • I was looking for constant array slices first and did not try to expand this code to both const and non const slices.

But one nice feature of the code below is, that you can take slices of slices...

// 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;
}
BitTickler
  • 10,905
  • 5
  • 32
  • 53
  • That looks like a bad (read: inefficient and inflexible) [C++20 / GSL `span`](https://en.cppreference.com/w/cpp/container/span)... aside from allowing non-contiguous sequences. – Deduplicator Oct 13 '18 at 00:04
  • Now you mention the c++ guys call it ``span``, I found it. They should have named it ``slice`` it I could have saved myself the work. But... @Deduplicator ... what is inefficient about this implementation? – BitTickler Oct 13 '18 at 00:05
  • You have three members: Reference to container, start of slice, length of slice. But for a random-access-container, you only need begin-terator and length or end-Iterator. Also, you should use `noexcept` and `constexpr`. – Deduplicator Oct 13 '18 at 00:08
  • But my container is random access (read only of course). And also, at first glance I think the ``std::span`` is also a matter of taste... they need the ``subspan`` function while my slice can pose as an array replacement for sub-functions. And it works both for array and for slice sources. And I also don't "go pointer" in my implementation. And last not least - with 1 extra function I could retrieve the source. That is one thing iterators usually do not offer AFAIK. – BitTickler Oct 13 '18 at 00:13
  • You know, ignorance of the container, aside from just enough to find all the elements in the sequence, is a feature and not a bug. It greatly increases flexibility at no extra cost. – Deduplicator Oct 13 '18 at 00:40