0

I am looking for a convenient and efficient way to initialize a static array in a function template. For example, let's say we have a function like this:


template <size_t windowSize>
int process_signal(int currentSignal)
{    
    // incorrect: inits only 3 elements; want to set all values to -1
    static std::array<int, windowSize> window = { -1, -1, -1 }; 
    // do something...
}

This way I can initialize only first 3 elements with -1, but not all of them.

The best solution I've come up so far is using constexpr function for compile-time initialization:

template <size_t sz, int value>
constexpr std::array<int, sz> init_array() 
{
    std::array<int, sz> arr{};
    //arr.fill(-1); // will be constexpr since C++20

    // operator [] is constexpr in C++17
    for (int i = 0; i < sz; ++i)
        arr[i] = value;

    return arr;
}


template <size_t windowSize>
int process_signal(int currentSignal)
{    
    // works fine, but extra variable needed
    constexpr static std::array<int, windowSize> init_value = init_array<windowSize, -1>();
    static std::array<int, windowSize> window = init_value;

    // window will be updated...
}

This way I can initialize the array once at compile-time. However, it requires an additional constexpr variable (in my case init_value) to bind the result of init_array() function. And if I try the same without it, init_array() is called as a normal function (so initialization is no longer compile-time):

template <size_t windowSize>
int process_signal(int currentSignal)
{    
    // no longer compile-time in MSVC2019
    static std::array<int, windowSize> window = init_array<windowSize, -1>();

    // window will be updated...
}

Is it possible to do the same without extra constexpr variable? Or, maybe, it is just specific to my compiler (MSVC 2019)?

Stack Danny
  • 7,754
  • 2
  • 26
  • 55
mentalmushroom
  • 2,261
  • 1
  • 26
  • 34
  • You could use template parameter pack expansion, here is [some inspiration](https://stackoverflow.com/a/52738638/7571258) (Method 3). – zett42 Feb 22 '20 at 14:56
  • 1
    Does this answer your question? [How to initialize all members of an array to the same value?](https://stackoverflow.com/questions/201101/how-to-initialize-all-members-of-an-array-to-the-same-value) – zett42 Feb 22 '20 at 15:05
  • 1
    -> [This answer](https://stackoverflow.com/a/54666996/7571258) – zett42 Feb 22 '20 at 15:05
  • @zett42 In this question I want to emphasize the difference between initialization of a normal variable with a constexpr function and a constexpr variable with respect to MSVC compiler. – mentalmushroom Feb 22 '20 at 15:21

1 Answers1

1

There is no need for your extra variable, just write:

static auto window = init_array<windowSize, -1>();

The initializer is still a constant expression and should be evaluated by the compiler at compile-time.

If your compiler is not optimizing this properly for some reason, then you can use a lambda to store the intermediate constexpr variable:

static auto window = []{ constexpr auto x = init_array<windowSize, -1>(); return x; }();

or you can put this in a separate function, e.g. in init_array:

template <size_t sz, int value>
constexpr std::array<int, sz> init_array() 
{
    constexpr auto x = []{
        std::array<int, sz> arr{};
        //arr.fill(-1); // will be constexpr since C++20

        // operator [] is constexpr in C++17
        for (int i = 0; i < sz; ++i)
            arr[i] = value;

        return arr;
    }();
    return x;
}

You can make the function much more generic:

template <std::size_t S, typename T>
constexpr auto init_array(const T& value) 
{
    return std::apply([&](auto... pack){
        return std::array{((void)pack, value)...};
    }, std::array<int, S>{});
}

used as

static auto window = init_array<windowSize>(-1);

or

template <std::size_t S, auto value>
constexpr auto init_array() 
{
    constexpr auto x = std::apply([](auto... pack){
        return std::array{((void)pack, value)...};
    }, std::array<int, S>{});
    return x;
}

used as

static auto window = init_array<windowSize, -1>;

to force compile-time evaluation (up to copying).

walnut
  • 21,629
  • 4
  • 23
  • 59
  • This is what I've written in my post: this way initialization is no longer compile-time in MSVC 2019 – mentalmushroom Feb 22 '20 at 15:14
  • 1
    @mentalmushroom the compiler decides how to handle it. Copying the constexpr value into static variable is probably more time consuming than running the simple function. – ALX23z Feb 22 '20 at 15:22
  • @mentalmushroom I have no idea why MSVC doesn't optimize this properly, but I have added an alternative. – walnut Feb 22 '20 at 15:23
  • 1
    @mentalmushroom After having a look at the assembly that MSVC generates, I would say that is a missed optimization. MSVC does not seem to recognize that it can perform constant initialization rather than dynamic initialization of the `static` if the initializer is a more complex constant expression. There is no way to *require* constant initialization, it just so happens to work if you use the `initial_value` `constexpr`. This optimization is up to the compiler. – walnut Feb 22 '20 at 15:39