3

Say I have a class that looks like the following

template<std::size_t NumThings>
class Bar
{
public:
    Bar(const int& v) : m_v(v) {}

    std::array<int,NumThings> GetThings() const
    {
        std::array<int,NumThings> things;
        for(std::size_t i = 0; i < NumThings; ++i)
            things[i] = m_v;
        return things;
    }

protected:
    const int& m_v;
}; 

template<std::size_t NumItems, std::size_t NumPieces>
class Foo
{
public:
    Foo(const int& initialValue)
    :
        m_array(/* what do I do to pass initialValue to each item? */)
    {}

protected:
    std::array<Bar<NumPieces>,NumItems> m_array;
};

I'm unsure how to initialise the array of Bar in the Foo class by passing the parameter. I suppose I can't use {...} sort of syntax because I don't know how many items yet, but I'm sure this there's some sort of meta-template programming trick I can use.

EDIT made default constructor of Bar impossible.

NeomerArcana
  • 1,978
  • 3
  • 23
  • 50

5 Answers5

1

For C++14 you can use lambda-expression.

If there is an option to add a default constructor & overload the operator= in Bar class:

Foo(const int initialValue)
:
    m_array(
        [initialValue] () {
            std::array<Bar<some_number>, NumItems> res;
            for (auto &val : res) {
                val = Bar<some_number>(initialValue);
            }
            return res;
        }()
    )
{}

Otherwise:

template<class T, std::size_t N, std::size_t ...Ns>
std::array<T, N> make_array_impl(
        std::vector<T> t,
        std::index_sequence<Ns...>)
{
    return std::array<T, N>{ *(t.begin() + Ns) ... };
}

template<class T, std::size_t N>
std::array<T, N> make_array(std::vector<T> t) {
    if(N > t.size())
        throw std::out_of_range("that's crazy!");
    return make_array_impl<T, N>(t, std::make_index_sequence<N>());
}

template<std::size_t NumItems, std::size_t BarsCount>
class Foo
{
public:
    Foo(const int initialValue)
            :
            m_array(
                    [initialValue]() {
                        std::vector<Bar<BarsCount>> vec;
                        for (size_t i = 0; i < NumItems; i++) {
                            vec.emplace_back(initialValue);
                        }
                        return make_array<Bar<BarsCount>, NumItems>(vec);
                    }()
            )
    {}

protected:
    std::array<Bar<BarsCount>, NumItems> m_array;
};

Reference for original make_array: https://stackoverflow.com/a/38934685/8038186


Edit:

If you can use a pointers' array instead of immidiate initialized objects' array, you can use the first solution, without having a default constructor or overloading the operator=:

Foo(const int initialValue)
:
    m_array(
        [initialValue] () {
            std::array<std::shared_ptr<Bar<some_number>>, NumItems> res;
            for (auto &val : res) {
                val = std::make_shared<Bar<some_number>>(Bar<some_number>(initialValue));
            }
            return res;
        }()
    )
{}
Coral Kashri
  • 3,436
  • 2
  • 10
  • 22
  • 1
    I've edited the question. Let's assume I can't have a default constructor. – NeomerArcana Oct 10 '19 at 23:03
  • @NeomerArcana do you have to use `std::array`, or you can use `std::vector`? – Coral Kashri Oct 10 '19 at 23:31
  • I'd prefer `std::array`, and I'd prefer not to delay initialisation through an array of pointers. – NeomerArcana Oct 10 '19 at 23:33
  • @NeomerArcana Re: "_I'd prefer not to delay initialisation through an array of pointers_" - There is _one_ pointer in a `std::vector`. That pointer makes it very fast whenever you need to move it etc. `std::array`s have to be copied, element by element if you need to move them. – Ted Lyngmo Oct 11 '19 at 06:36
  • Thanks @TedLyngmo I'm aware of this. My aim is to only ever need to pass the arrays by reference. – NeomerArcana Oct 11 '19 at 06:39
  • @NeomerArcana Ok, so no need for move semantics then. But what do you mean by "_array of pointers_" ? – Ted Lyngmo Oct 11 '19 at 06:46
  • My `m_array` could be an array of pointers, meaning that they don't need to be initialised immediately. – NeomerArcana Oct 11 '19 at 06:55
  • @NeomerArcana Are you talking about another hypothetical `m_array` than the one in your question? Are you doing this with hope of _never_ having to use `vector`s? – Ted Lyngmo Oct 11 '19 at 07:51
  • @NeomerArcana The solution is much more easier when its array of pointers. In this case you can use the first solution in my answer, because the default value is `null`, and the `operator=` can assign new objects into the array. – Coral Kashri Oct 11 '19 at 09:41
1

There is no standard function for constructing an array of N instances of T. But you can implement this function yourself. One way of achieving this is to form a parameter pack the same size as the array you want to initialize. Then, you can unpack that parameter pack inside an array object's initializer list to construct the array correctly. Here is an example :

#include <array>
#include <cstddef>  // std::size_t
#include <utility>  // std::index_sequence

namespace details {
    template<class T, std::size_t ... I>
    std::array<T, sizeof...(I)> make_array_impl(const T & p_Value, std::index_sequence<I...>)
    {
        return {
            (I, p_Value)...
        };
    }
}   // namespace details


template<std::size_t N, class T>
auto make_array(const T & p_Value)
{
    return details::make_array_impl(p_Value, std::make_index_sequence<N>());
}

The make_array function converts the template argument N to a parameter pack using an std::index_sequence. For example, if N is 3, the template argument I... for make_array_impl will be <0, 1, 2>.

Then, in make_array_impl that parameter pack is expanded in (I, p_value).... To expand a parameter pack, you have to use it in some way. But we don't actually care about the values of I, only by how many values it holds. Again, if N was 3, the parameter pack will expand to return { (0, p_Value), (1, p_Value), (2, p_Value) };. Applying the comma operator, the evaluation will be equivalent to return { p_Value, p_Value, p_Value };.

The usage in your case would look like this :

Foo(const int& initialValue) :
        m_array(make_array<NumItems>(Bar<NumPieces>{ initialValue }))
    {}
François Andrieux
  • 28,148
  • 6
  • 56
  • 87
1

You can make use of std::make_index_sequence to get a parameter pack with the right size. One way of doing it is partially specializing Foo.

template<std::size_t NumItems, std::size_t NumPieces, typename = std::make_index_sequence<NumItems>>
class Foo;

template<std::size_t NumItems, std::size_t NumPieces, std::size_t ... Count>
class Foo<NumItems, NumPieces, std::index_sequence<Count...>>
{
public:
    Foo(const int& initialValue)
    :
        m_array{(Count, initialValue)...}
    {}

protected:
    std::array<Bar<NumPieces>,NumItems> m_array;
};

This will however generate warnings, gcc says

prog.cc:30:23: warning: left operand of comma operator has no effect [-Wunused-value] 30 | m_array{(Count, initialValue)...}

super
  • 12,335
  • 2
  • 19
  • 29
0

One option is to have a default constructor in Bar.

Bar() : Bar(0) {}
Bar(const int v) : m_v(v) {}

and then set the values of each item in the function body of Foo's constructor.

Foo(const int initialValue)
{
    std::fill_n(m_array, NumItems, Bar(initialValue));
}
R Sahu
  • 204,454
  • 14
  • 159
  • 270
0
Foo(const int initialValue)
{
    std::fill_n(m_array, NumItems, initialValue);
}
bloody
  • 1,131
  • 11
  • 17