DRY way to construct all elements of an array with the same initializer list?

187 views Asked by At

In C++11, is there a DRY way to construct all elements of an array with some same set of parameters for all elements? (e.g. via a single initializer list?)

For example:

class C {
public:
   C() : C(0) {}
   C(int x) : m_x{x} {}
   int m_x;
};

// This would construct just the first object with a parameter of 1.
// For the second and third object the default ctor will be called.
C ar[3] {1};

// This would work but isn't DRY (in case I know I want all the elements in the array to be initialized with the same value.
C ar2[3] {1, 1, 1};

// This is DRYer but obviously still has repetition.
const int initVal = 1;
C ar3[3] {initVal, initVal, initVal};

I know my goal is easily achievable by using an std::vector. I'm wondering if it's possible with raw arrays as well.

4

There are 4 answers

9
Richard Hodges On BEST ANSWER

c++14 - a little work will make this work for c++11

#include <iostream>
#include <array>
#include <utility>

class C {
public:
    C() : C(0) {}
    C(int x) : m_x{x} {}
    int m_x;
};

namespace detail {
    template<class Type, std::size_t...Is, class...Args>
    auto generate_n_with(std::index_sequence<Is...>, const Args&...args)
    {
        return std::array<Type, sizeof...(Is)> {
            {(void(Is), Type { args... })...} // Or replace '{ args... }' with '( args... )'; see in comments below.
        };
    }
}

template<class Type, std::size_t N, class...Args>
auto generate_n_with(const Args&...args)
{
    return detail::generate_n_with<Type>(std::make_index_sequence<N>(), args...);
}

int main()
{
    auto a = generate_n_with<C, 3>(1);
    for (auto&& c : a)
    {
        std::cout << c.m_x << std::endl;
    }
}

results:

1
1
1

I want to guarantee no copies prior to c++17

The you would need to generate into a vector:

template<class Container, class...Args>
auto emplace_n(Container& c, std::size_t n, Args const&...args)
{
    c.reserve(n);
    while(n--) {
        c.emplace_back(args...);
    }
};

used like this:

std::vector<C> v2;
emplace_n(v2, 3, 1);
1
Dietmar Kühl On

You can construct a sequence of elements using an std::index_sequence<...> and expand that into the initializers of an array. I don't know of any approach avoiding an auxiliary function, though. Here is an example:

#include <iterator>
#include <algorithm>
#include <iostream>

struct S {
    int value;
    S(int value): value(value) {}
};
std::ostream& operator<< (std::ostream& out, S const& s) {
    return out << s.value;
}

#include <array>
#include <iterator>
#include <algorithm>
#include <iostream>

struct S {
    int value;
    S(int value): value(value) {}
};
std::ostream& operator<< (std::ostream& out, S const& s) {
    return out << s.value;
}

template <typename T, std::size_t... I>
std::array<T, sizeof...(I)> fill_aux(T value, std::index_sequence<I...>)
{
    return std::array<T, sizeof...(I)>{ (void(I), value)... };
}
template <std::size_t N, typename T>
std::array<T, N> fill(T value) {
    return fill_aux(value, std::make_index_sequence<N>());
}

int main()
{
    std::array<S, 10> array = fill<10>(S(17));
    std::copy(array.begin(), array.end(), std::ostream_iterator<S>(std::cout, " "));
}
0
Vaughn Cato On

By creating derived class, you can effectively create a new default value. It's a bit hackish, but may be less hackish than other solutions. Here's an example:

class C {
public:
   C() : C(0) {}
   C(int x) : m_x{x} {}
   int m_x;
};


template <int init>
struct CInit : C { CInit() : C(init) {} };

CInit<1> ar2[3];


const int initVal = 1;
CInit<initVal> ar3[3];

Another approach is to wrap your raw array inside a struct with a variadic constructor:

template <size_t n>
struct Array {
    C array[n];

    template <size_t... seq>
    Array(int init,std::index_sequence<seq...>)
    : array{(void(seq),init)...}
    {
    }

    Array(int init)
    : Array(init,std::make_index_sequence<n>())
    {
    }
};


const int initVal = 1;
Array<3> ar3_1(initVal);
const C (&ar3)[3] = ar3_1.array;
1
Danra On

Building on Richard's answer, it's also possible to define

template<class Type, std::size_t N, class...Args>
auto generate_n_with(const std::array<Type, N>&, const Args&...args)
{
    return detail::generate_n_with<Type>(std::make_index_sequence<N>(), args...);
};

Allowing you to enter the array as a parameter to make the code more dry in case you already know the type of the array, e.g.

class D {
public:
    D();
    std::array<int, 3> m_ar;
};

Allowing

D::D() : m_ar{generate_n_with{m_ar, 5}} {}

Instead of the less DRY

D::D() : m_ar{generate_n_with<int, 3>{5}} {}

P.S. maybe there's an even DRYer way without repeating m_ar twice?