6

std::array can be constructed (at compile time with newer C++ versions) with specific values, e.g.

std::array a{1, 4, 9};

however - it does not have a constructor, or a standard-library named constructor idiom, taking a single value and replicating it. i.e. we don't have:

std::array<int, 3> a{11};
// a == std::array<int, 3>{11, 11, 11};

How can we, therefore, construct an array given just the value to repeat?

Edit: I'm looking for a solution which would work even for element types which are not default constructible; so, a solution going through default-constructing the array, then filling it, is not what I'm after - despite the fact that this will work for the case of int (like in the example).

einpoklum
  • 118,144
  • 57
  • 340
  • 684

2 Answers2

8

We can write an appropriate named constructor idiom to achieve this. Then, your example definition would look like:

auto a = array_repeat<3>(11);
// a == std::array<int, 3>{11, 11, 11};

The implementation is a bit clunky, however, as we need to use the "indices trick" that takes a lot of boilerplate in C++11, so let's assume C++14:

namespace detail {

template<size_t, class T>
constexpr T&& identity(T&& x) { return std::forward<T>(x); }

template<class T, size_t... Indices>
constexpr auto array_repeat_impl(T&& x, std::index_sequence<Indices...>)
{
    return std::experimental::make_array(identity<Indices>(x)...);
}

} // end detail

template<size_t N, class T>
constexpr auto array_repeat(T&& x)
{
    return detail::array_repeat_impl(std::forward<T>(x), std::make_index_sequence<N>());
}

See this working on GodBolt.

If you can compile your code C++20, you can drop the dependency on make_array and write:

namespace detail {

template<size_t, class T>
constexpr T&& identity(T&& x) { return std::forward<T>(x); }

template<class T, size_t... Indices>
constexpr auto array_repeat_impl(T&& x, std::index_sequence<Indices...>)
{
    return std::array{identity<Indices>(x)...};
}

} // end detail

template<size_t N, class T>
constexpr auto array_repeat(T&& x)
{
    return detail::array_repeat_impl(std::forward<T>(x), std::make_index_sequence<N>());
}

GodBolt

Notes:

  • This solution is somewhat similar to Jared Hoberock's tuple_repeat, part of his tuple utilities for C++11.
  • Thanks goes to @Caleth and @L.F. for pointing out a inappropriate forwarding in array_repeat_impl.
  • The array_repert() function take rvalue references and moves from them, so it's ok to create a temporary in the argument, even if it's expensive.
einpoklum
  • 118,144
  • 57
  • 340
  • 684
1

With C++20, you can make a helper for this straightforwardly when the type is default-constructible and copyable by using the now-constexpr fill function (live example):

#include <array>
#include <concepts>
#include <cstddef>

template<std::size_t N, std::semiregular T>
constexpr auto array_repeat(const T& value) -> std::array<T, N> {
    std::array<T, N> ret;
    ret.fill(value);
    return ret;
}

int main() {
    constexpr auto a = array_repeat<3>(11);
    static_assert(a == std::array<int, 3>{11, 11, 11});
}

This could potentially be less efficient, but whether that's a problem is up to you.

chris
  • 60,560
  • 13
  • 143
  • 205
  • Chris, I have to apologize, since I meant to ask about the case where you can't just default-construct-and-fill, and have to construct from the get-go with the repeated value. Perhaps I should also alter the example to reflect that. Again, my apology. – einpoklum Sep 10 '20 at 06:56
  • @einpoklum, It's fine, I suspected this wouldn't be useful to you specifically, but I hope someone will find it useful who didn't otherwise know this was a possibility. – chris Sep 10 '20 at 15:44