0
// sz can only be 3 or 4
template<int sz>
void func() {
 int arr[sz]; 
}

I would like to initialize arr to be all ones without a loop. Is this possible to do? I'm envisioning something like

int arr[sz] = {1, 1, 1,...,1};

but for a variable sized array.

I think one approach is

int arr[sz] = (sz == 4) ? {1, 1, 1, 1} : {1, 1, 1};
24n8
  • 1,898
  • 1
  • 12
  • 25
  • Why not just use `std::array`? – 康桓瑋 Apr 28 '22 at 03:30
  • 1
    @康桓瑋 Woudn't I run into the same issue? I don't think there's a constructor for `std::array` that fills the array to a constant value – 24n8 Apr 28 '22 at 03:36
  • While there is no way that I know of to accomplish this at construction, `std::array` does have a `fill` function that can be called after the array is constructed. – jkb Apr 28 '22 at 03:41

2 Answers2

0

This is much simpler if you use std::array, i.e. you can use the immediately invoke lambda to return the corresponding array based on the value of sz

// sz can only be 3 or 4
template<int sz>
void func() {
  std::array<int, sz> arr = []() -> std::array<int, sz> {
    if constexpr (sz == 4) 
      return {1, 1, 1, 1};
    else
      return {1, 1, 1};
  }();
}

Since C++17 guarantees copy elision, copy/move construction is not involved here.


C++14 solution

std::array<int, 3> 
gen_arr(std::integral_constant<int, 3>) {
  return {1, 1, 1};
}

std::array<int, 4> 
gen_arr(std::integral_constant<int, 4>) {
  return {1, 1, 1, 1};
}

// sz can only be 3 or 4
template<int sz>
void func() {
  std::array<int, sz> arr = gen_arr(std::integral_constant<int, sz>{});
}
康桓瑋
  • 33,481
  • 5
  • 40
  • 90
  • 2
    Might be worth adding `static_assert(sz == 3 || sz == 4, "sz must be 3 or 4");` above the `if`. – Remy Lebeau Apr 28 '22 at 04:24
  • Is the `if constexpr` needed? We can't currently compile with C++17, only 14 or earlier – 24n8 Apr 28 '22 at 12:26
  • @RemyLebeau If we explicitly instantiate the templates for `sz=3,4`, would that remove the need to `static_assert`? – 24n8 Apr 28 '22 at 13:29
  • @24n8 The `static_assert` just helps to ensure you can't ever instantiate the template with other unsupported values. In C++14 and earlier, you can use SFINAE (ie, `std::enable_if`) or template specialization to achieve a similar result as the `if constexpr`, but it will require more code, as you will have to duplicate the code for each value of `sz`. – Remy Lebeau Apr 28 '22 at 14:00
  • @RemyLebeau Yes, I understand what the `static_assert` does, but I was under the impression that if you explicitly instantiate templates (e.g., in this case we'd explicitly instantiate for `sz == 3` and `sz = =4`), then if someone tries to instantiate for some other `sz` value, the linker will complain? Is this understanding wrong? If it's correct, then it seems the `static_assert` won't be needed? – 24n8 Apr 28 '22 at 14:04
  • "*then if someone tries to instantiate for some other sz value, the linker will complain*" It depends. In my previous example, if you specified `sz` as 2, the compiler would complain that `{1, 1, 1}` could not be converted to `array`. And in the C++14 example, the compiler will complain that it can't find a matching function. None of which are as user-friendly as `static_assert` with a *clear* error message. – 康桓瑋 Apr 28 '22 at 14:14
  • 1
    @24n8 The C++17 solution presented allows any value of `sz`, but only 3 and 4 will initialize the array correctly. Values greater than 4 will fill the array wilth zeros, and values less than 3 will fail to compile (not link) due to too many initializers. The C++14 solution doesn't suffer from that, it will just fail to compile (unknown function) for values other than 3 and 4. The`static_assert` lets you provide your own meaningful error message. – Remy Lebeau Apr 28 '22 at 14:20
0

The other solutions work great and the suggestion of using std::array is definitely the best idea. If you want to allow more options you could revert to variadic templates:

#include <array>
#include <algorithm>
#include <iostream>
#include <iterator>

template<std::size_t... I>
constexpr auto ones_impl(std::index_sequence<I...>) {
    return std::array<int, sizeof...(I)>{ (I, 1)... };
}
template<std::size_t N>
constexpr auto ones() {
    return ones_impl(std::make_index_sequence<N>{});
}

template<int sz>
void func() {
    auto arr = ones<sz>();

    copy(arr.begin(), arr.end(), std::ostream_iterator<int>(std::cout, ", "));
    std::cout << '\n';
}

int main()
{
    std::cout << "funct<3>(): ";
    func<3>();
    std::cout << "funct<4>(): ";
    func<4>();
    std::cout << "funct<7>(): ";
    func<7>();
}

Due credit to this answer.

Costantino Grana
  • 3,132
  • 1
  • 15
  • 35