24

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;
}
1
  • 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. Commented Jan 15, 2017 at 10:31

2 Answers 2

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

Sign up to request clarification or add additional context in comments.

5 Comments

What about the case of constructing from a lambda which depends on the sequential index?
@Danra I've amended the answer.
It doesn't seem to work if the function returns a non-copyable class melpon.org/wandbox/permlink/ld6OcmMdKyNVom5m , any ideas how to fix that?
@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.
@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.
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).

9 Comments

This has the same issue as Edgar's answer, it doesn't work for classes which have no copy constructor
@Danra : Okay, so..? Your question makes no mention of copyable types being an issue in any respect.
I'm looking for a general method. The fact that this doesn't work for non-copyable return types is a limitation.
@Danra : Note that in C++17, due to its newly-guaranteed copy-elision, the code in my answer will work: demo. :-] Answer updated accordingly.
@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).
|

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.