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.

0

5 Answers 5

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 }))
    {}
Sign up to request clarification or add additional context in comments.

Comments

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

Comments

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

9 Comments

I've edited the question. Let's assume I can't have a default constructor.
@NeomerArcana do you have to use std::array, or you can use std::vector?
I'd prefer std::array, and I'd prefer not to delay initialisation through an array of pointers.
@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::arrays have to be copied, element by element if you need to move them.
Thanks @TedLyngmo I'm aware of this. My aim is to only ever need to pass the arrays by reference.
|
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));
}

5 Comments

then you don't need the default ctor anyway :)
@bloody, yes you would. Otherwise, m_array cannot be initialized at all.
Sorry, right. It must be first created, then filled.
Doesn't it have to be std::fill_n(m_array.data(), NumItems, Bar<BarThings>(initialValue)); ?
I've edited the question. Let's assume I can't have a default constructor.
0
Foo(const int initialValue)
{
    std::fill_n(m_array, NumItems, initialValue);
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.