Creating a structure with an expanded index sequence

1.2k views Asked by At

I want to be able to use parameter pack expansion in the initializer list of constructors. Is the best way to achieve this, to endow my class with a parameter pack template argument? Here is an example of what I mean: https://coliru.stacked-crooked.com/a/e699c4cd035e0b1c

#include <utility>
#include <iostream>

template<typename T, std::size_t...Is>
struct base_vec
{
    constexpr static std::size_t N = sizeof...(Is);
    T e[N];
    base_vec() : e{} {}
    explicit base_vec(const T& s) : e{((void)Is,s)...} {}
};


template<typename T, std::size_t...Is>
std::ostream& operator<<(std::ostream& lhs, const base_vec<T,Is...>& rhs)
{
    return (std::cout << ... << rhs.e[Is]);
}



template<typename T, std::size_t...Is>
constexpr auto getVecIs(std::index_sequence<Is...> seq)
{
    return base_vec<T, Is...>{};
}




template<typename T, std::size_t N>
using vec = decltype(getVecIs<T>(std::declval<std::make_index_sequence<N>>()));

int main()
{
    vec<int,3> v(2);
    std::cout << v << "\n";
    return 0;
}

Note the trivial expansion e{((void)Is,s)...}. Is this an ok practice, or am I missing some minus of this approach (except for the fact that now my structure will have a whole parameter pack, as opposed to a single size_t N)?

2

There are 2 answers

8
Jarod42 On BEST ANSWER

You can move the expansion entirely inside the class:

template<typename T, std::size_t N>
struct vec
{
public:
    T e[N];
    vec() : e{} {}
    explicit vec(const T& s) : vec{s, std::make_index_sequence<N>{}} {}
private:
    template <std::size_t ... Is>
    vec(const T& s, std::index_sequence<Is...>) : e{(Is, s)...} {}
};
7
max66 On

It seems to me that you're recreating a class inspired to std::array with an handy index sequence to operate.

Intriguing.

I propose a simplification (IMHO) that directly define vec, avoiding base_vec, using template specialization

#include <utility>
#include <iostream>

template <typename T, std::size_t N, typename = std::make_index_sequence<N>>
struct vec;

template <typename T, std::size_t N, std::size_t...Is>
struct vec<T, N, std::index_sequence<Is...>>
 {
    T e[N] {} ;

    vec (const T& s) : e{((void)Is,s)...}
     { }
 };


template <typename T, std::size_t...Is>
std::ostream& operator<< (
   std::ostream& lhs,
   vec<T, sizeof...(Is), std::index_sequence<Is...>> const & rhs)
 { return (std::cout << ... << rhs.e[Is]); }

int main ()
 {
    vec<int, 3u> v(2);

    std::cout << v << "\n";
 }

You can add, in the body of the vec specialization, a static_assert() as follows

static_assert( N == sizeof...(Is) );

to avoid "hijacked" uses as follows

vec<int, 5u, std::make_index_sequence<12u>>  bad_vec_1;

or, maybe better,

static_assert( std::is_same_v<std::index_sequence<Is...>,
                              std::make_index_sequence<N>> );   

to avoid aslo

vec<int, 5u, std::index_sequence<1u, 3u, 5u, 2u, 4u>>  bad_vec_2;

-- EDIT --

The OP asks

Any ideas how to extend this to multiple index sequences - for multi-dimensional arrays for example?

It's simple.

As suggested by Jarod42, you can add another N and anther std::index_sequence.

I propose as follows for the 2 dim case.

I've also added a third index sequence for N1*N2 (I suppose can be useful).

I leave the body of the specialization as exercise.

template <typename T, std::size_t N1, std::size_t N2,
          typename = std::make_index_sequence<N1>,
          typename = std::make_index_sequence<N2>,
          typename = std::make_index_sequence<N1*N2>>
struct vec2dim;

template <typename T, std::size_t N1, std::size_t N2,
          std::size_t ... Is, std::size_t ... Js, std::size_t ... Ks>
struct vec2dim<T, N1, N2, std::index_sequence<Is...>,
               std::index_sequence<Js...>, std::index_sequence<Ks...>>
 {
   static_assert( std::is_same_v<std::index_sequence<Is...>,
                                 std::make_index_sequence<N1>> );
   static_assert( std::is_same_v<std::index_sequence<Js...>,
                                 std::make_index_sequence<N2>> );
   static_assert( std::is_same_v<std::index_sequence<Ks...>,
                                 std::make_index_sequence<N1*N2>> );
   // ...
 };