Join the Stack Overflow Community
Stack Overflow is a community of 6.6 million programmers, just like you, helping each other.
Join them; it only takes a minute:
Sign up

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;
}
share|improve this question
    
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 yesterday
1  
Some code to show what you hope to achieve is in order. – StoryTeller yesterday
    
@StoryTeller Added – Danra yesterday
1  
@Danra: The code must be embedded in the question, not provided via an external link. – Christian Hackl yesterday
1  
@ChristianHackl Thanks for the comment, done. – Danra yesterday
up vote 8 down vote accepted

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

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

I suppose something like this might work:

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

Then 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>
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>
auto create_array(F func) {
    return create_array_impl<T, N>(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

share|improve this answer
    
What about the case of constructing from a lambda which depends on the sequential index? – Danra yesterday
    
@Danra I've amended the answer. – Edgar Rokyan yesterday
    
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 yesterday
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. – hvd yesterday
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. – hvd yesterday

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.