23

How can I construct an std::array with an index sequence, or a lambda which depends on a sequential index?

std::iota and std::generate seem relevant, but I'm not sure how to use them to construct an std::array, rather then apply them on one which is already constructed (which isn't possible in case the element type of the array isn't default-constructible).

Example of the kind of code I'd like to DRY:

#include <array>

class C
{
public:
    C(int x, float f) : m_x{x}, m_f{f} {}
private:
    int m_x;
    float m_f;
};

int main()
{
    std::array<int, 10> ar = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
    std::array<C, 3> ar2 = {C{0, 1.0}, C{1, 1.0}, C{2, 1.0}};
    return 0;
}
Edgar Rokjān
  • 17,245
  • 4
  • 40
  • 67
Danra
  • 9,546
  • 5
  • 59
  • 117
  • It is not possible to iterate over elements of a `std::array` (by index, or any other means) before it has been constructed. You might try aggregate initialisation. – Peter Jan 15 '17 at 10:31

2 Answers2

17

The next approach should work for you:

template<typename T, std::size_t N, std::size_t... I>
constexpr auto create_array_impl(std::index_sequence<I...>) {
    return std::array<T, N>{ {I...} };
}

template<typename T, std::size_t N>
constexpr auto create_array() {
    return create_array_impl<T, N>(std::make_index_sequence<N>{});
}

You can create an array like:

constexpr auto array = create_array<std::size_t, 4>();

wandbox example

One can modify the aforementioned solution to add a lambda in the next way:

template<typename T, std::size_t N, typename F, std::size_t... I>
constexpr auto create_array_impl(F&& func, std::index_sequence<I...>) {
    return std::array<T, N>{ {func(I)...} };
}

template<typename T, std::size_t N, typename F>
constexpr auto create_array(F&& func) {
    return create_array_impl<T, N>(std::forward<F>(func), std::make_index_sequence<N>{});
}

And then use:

const auto array = create_array<std::size_t, 4>([](auto e) {
    return e * e;
});

wandbox example

Edgar Rokjān
  • 17,245
  • 4
  • 40
  • 67
  • What about the case of constructing from a lambda which depends on the sequential index? – Danra Jan 15 '17 at 10:47
  • @Danra I've amended the answer. – Edgar Rokjān Jan 15 '17 at 11:14
  • It doesn't seem to work if the function returns a non-copyable class http://melpon.org/wandbox/permlink/ld6OcmMdKyNVom5m , any ideas how to fix that? – Danra Jan 15 '17 at 11:22
  • 2
    @Danra Your class is non-copyable and also non-movable. If you make it movable, but keep it non-copyable, then it already works, with the minor nit of needing to change `for (auto e : array)` to `for (auto & e : array)` or `for (auto && e : array)` to prevent copies of the array elements from being made. –  Jan 15 '17 at 11:42
  • 3
    @Danra Additionally, in C++17, it will work even with non-movable classes, since RVO will become required and there will no longer be a need to check whether a move constructor is available. You're testing with GCC 6.1 set to `-std=gnu++1z`, but GCC 6 doesn't implement this yet, that's coming in GCC 7. –  Jan 15 '17 at 11:52
14

For ar, here's an approach:

namespace detail {
  template<typename T, T... Ns>
  constexpr auto make_iota_array(T const offset, std::integer_sequence<T, Ns...>) noexcept
   -> std::array<T, sizeof...(Ns)> {
    return {{(Ns + offset)...}};
  }
}

template<typename T, T N>
constexpr auto make_iota_array(T const offset = {}) noexcept {
  static_assert(N >= T{}, "no negative sizes");
  return detail::make_iota_array<T>(offset, std::make_integer_sequence<T, N>{});
}

// ...

auto ar = make_iota_array<int, 10>(99);

Online Demo

For ar2, here's an approach:

namespace detail {
  template<typename T, typename F, std::size_t... Is>
  constexpr auto generate_array(F& f, std::index_sequence<Is...>)
   -> std::array<T, sizeof...(Is)> {
    return {{f(std::integral_constant<std::size_t, Is>{})...}};
  }
}

template<typename T, std::size_t N, typename F>
constexpr auto generate_array(F f) {
  return detail::generate_array<T>(f, std::make_index_sequence<N>{});
}

// ...

auto ar2 = generate_array<C, 3>([](auto i) -> C { return {i, i * 1.12f}; });

Online Demo

(noexcept is more-or-less optional here IMO, and omitted here for brevity, but is present in the demos.)

N.b. both are fully constexpr, but since generate_array is likely to be used with lambdas it won't be constexpr in practice until C++17 (demo). Also n.b. generate_array will work with non-copyable/non-movable types in C++17 due to guaranteed copy elision (demo).

Community
  • 1
  • 1
ildjarn
  • 62,044
  • 9
  • 127
  • 211
  • This has the same issue as Edgar's answer, it doesn't work for classes which have no copy constructor – Danra Jan 15 '17 at 11:26
  • @Danra : Okay, so..? Your question makes no mention of copyable types being an issue in any respect. – ildjarn Jan 15 '17 at 11:37
  • I'm looking for a general method. The fact that this doesn't work for non-copyable return types is a limitation. – Danra Jan 15 '17 at 11:40
  • @Danra : Aggregate-initializing arrays copy-initializes them. That's what the standard mandates, there's no way around it. If you want standard citations to back this up I can provide them, but the bottom line is what you're asking for simply is not possible. – ildjarn Jan 15 '17 at 11:42
  • Hmm, this seems to work fine though http://melpon.org/wandbox/permlink/2IX5ThhnM8362Rn7 – Danra Jan 15 '17 at 11:45
  • 2
    @Danra : Note that in C++17, due to its newly-guaranteed copy-elision, the code in my answer _will_ work: [demo](http://melpon.org/wandbox/permlink/xpeIgu9xpEcbazvR). :-] Answer updated accordingly. – ildjarn Jan 15 '17 at 11:54
  • A great example of why guaranteed copy-elision is great. Thanks! – Danra Jan 15 '17 at 11:56
  • Is there an advantage to handing `std::integral_constant{}` to `f` instead of just `Is`? – Danra Jan 15 '17 at 12:04
  • 2
    @Danra : Since your callable will receive a `integral_constant` rather than a `size_t` (assuming it uses `auto`), your callable can then use the value in constant expressions (e.g. as a template argument). – ildjarn Jan 15 '17 at 12:05