Sum two std::array of non-default-constructible types
Asked Answered
N

2

7

If I want to write a function summing two std::array, I would do something like:

template<class T, std::size_t N>
auto sum(const std::array<T, N>& a, const std::array<T, N>& b)
{
    std::array< decltype(a[0] + b[0]), N> result; //the underlying type is not necessarily T
    for (std::size_t i = 0; i < N; ++i)
    {
        result[i] = a[i] + b[i];
    }
    return result;
} 

How would I write this function in case decltype(a[0] + b[0]) is not default-constructible?

Here is an example on compiler explorer: https://godbolt.org/z/qndGfhehM

Ideally, I want to directly create and initialize the std::array, but I failed to write a function for any N.

I would like to rely only on a C++ standard.

I guess the implementation can be achieved with an intermediate std::vector, but it would require copies.

I tried to write a recursive template function such as:

template<class T, std::size_t N>
auto operator+(const std::array<T, N>& a, const std::array<T, N>& b)
{
    if constexpr (N > 1)
    {
        return {a[0] + b[0], ???};
    }
    else
    {
        return {a[0] + b[0]};
    }
}

but I don't know what to write instead of the ???.

I am aware that std::index_sequence exists, but I don't know how to use it in this case.

Nonet answered 28/11, 2023 at 14:27 Comment(3)
index_sequence tricks are clever, but if you need to use them, it's a sign of a design issue. It's rarely a good idea to not have a default constructor.Topflight
A std::array is "an aggregate type with the same semantics as a struct holding a C-style array T[N] as its only non-static data member." Since SumA is not default constructable, SumA arr[N] is not either.Definiens
@HolyBlackCat: Not all type would have correct default values. Initialization is better than assignation IMO, I don't see any design issue for that in general.Ajmer
R
7

Something along these lines:

template <typename T, std::size_t N, std::size_t... Is>
auto sum_helper(const std::array<T, N>& a, const std::array<T, N>& b,
                std::index_sequence<Is...>) {
    return std::array<decltype(a[0] + b[0]), N>{(a[Is] + b[Is])...};
}

template<class T, std::size_t N>
auto sum(const std::array<T, N>& a, const std::array<T, N>& b)
{
    return sum_helper(a, b, std::make_index_sequence<N>{});
} 

Demo

Reckon answered 28/11, 2023 at 14:35 Comment(3)
Your solution works, thanks. Pack expansion is a part of C++ that I never experienced, I cppreference does not give a lot of details...Nonet
I wondered if this solution is more efficient than the naive implementation, so I made a quick bench. It is a bit faster for small arrays, but becomes more and more significant as the size of the arrays grows.Nonet
I suggest marking the helper static.Tenia
T
12

Using std::index_sequence is a two-step process:

Create it and pass it to a helper which uses the indices for your task.
A generic lambda with explicit template-arguments is enough for that (for earlier standards, use a normal function and more arguments):

template <class T, std::size_t N>
auto operator+(const std::array<T, N>& a, const std::array<T, N>& b)
{
    return [&]<std::size_t... Is>(std::index_sequence<Is...>){
        return std::array<decltype(a[0] + b[0]), N>{(a[Is] + b[Is])...};
    }(std::make_index_sequence<N>());
}
Tenia answered 28/11, 2023 at 14:34 Comment(4)
I guess you could write just return std::array{(a[Is] + b[Is])...};.Phenylalanine
@DanielLangr Yes, but OP's code already relies on C++20 anyway.Phenylalanine
@Phenylalanine You're right, my response should have addressed your comment under that other answer. But you've removed it already :)Kraemer
@Phenylalanine The devil is in the details. For N==0, that would not actually do.Tenia
R
7

Something along these lines:

template <typename T, std::size_t N, std::size_t... Is>
auto sum_helper(const std::array<T, N>& a, const std::array<T, N>& b,
                std::index_sequence<Is...>) {
    return std::array<decltype(a[0] + b[0]), N>{(a[Is] + b[Is])...};
}

template<class T, std::size_t N>
auto sum(const std::array<T, N>& a, const std::array<T, N>& b)
{
    return sum_helper(a, b, std::make_index_sequence<N>{});
} 

Demo

Reckon answered 28/11, 2023 at 14:35 Comment(3)
Your solution works, thanks. Pack expansion is a part of C++ that I never experienced, I cppreference does not give a lot of details...Nonet
I wondered if this solution is more efficient than the naive implementation, so I made a quick bench. It is a bit faster for small arrays, but becomes more and more significant as the size of the arrays grows.Nonet
I suggest marking the helper static.Tenia

© 2022 - 2025 — McMap. All rights reserved.