1

The following code issues a warning when compiling with gcc, but only with version <= 9.3

#include <array>
#include <iostream>

template <std::size_t size>
struct A {
using atype = std::array<double, size>;

template <std::size_t index = 0>
static constexpr void fill_array(atype& res)
{
    std::get<index>(res) = 1;
    if constexpr (index < (size - 1))
           fill_array<index + 1>(res);
}
static constexpr atype get_array()
{
    atype res;
    fill_array(res);
    return res;
}
};


int main()
{
    auto x = A<3>::get_array();
    for (const auto e: x)
       std::cout << e << ' ';
}

Test it on godbolt. I am compiling with -Wall -pedantic -std=c++17 -O3. The issued warning is

In file included from <source>:1:

<source>: In instantiation of 'static constexpr A<size>::atype A<size>::get_array() [with long unsigned int size = 3; A<size>::atype = std::array<double, 3>]':

<source>:26:30:   required from here

/opt/compiler-explorer/gcc-9.3.0/include/c++/9.3.0/array:94:12: note: 'using atype = struct std::array<double, 3>' {aka 'struct std::array<double, 3>'} has no user-provided default constructor

   94 |     struct array

      |            ^~~~~

/opt/compiler-explorer/gcc-9.3.0/include/c++/9.3.0/array:110:56: note: and the implicitly-defined constructor does not initialize 'double std::array<double, 3>::_M_elems [3]'

  110 |       typename _AT_Type::_Type                         _M_elems;

      |                                                        ^~~~~~~~

Compiler returned: 0

While the std::array of double is uninitialized, actually the recursive template routine initializes all elements of res. So, the warning is not "real". In principle, I could "hack" the code by calling fill_array<1>, thereby skipping the initialization of the 0 component. But code for template functions is generated only when instantiated with given template parameters, so again, in the above code the compiler never skips to generate fill_array<0>.

Stragely, this warning only appears up to version 9.3 of gcc. Also, clang does not issue the warning. Even more strangely, when the functions are not embedded into a class, the warning disappear. With the following code:

#include <array>
#include <iostream>

constexpr std::size_t size = 3;

using atype = std::array<double, size>;

template <std::size_t index = 0>
void fill_array(atype& res)
{
    std::get<index>(res) = 1;
    if constexpr (index < (size - 1))
           fill_array<index + 1>(res);
}

atype get_array()
{
    atype res;
    fill_array(res);
    return res;
}

int main()
{
    auto x = get_array();
    for (const auto e: x)
       std::cout << e << ' ';
}

no warning are displayed, despite being apparently identical to the first code. Test it here.

  • Is there a reason for the different compiler behavior between the two codes?

  • Can I suppress the warning only for this variable, without introducing an overhead?

francesco
  • 7,189
  • 7
  • 22
  • 49

1 Answers1

3

it just looks like a bug in older version of static analyzer, which couldn't detect fact that you assign values to the elements and were warning you about using an uninitialized class and message can't be avoided by compiler flags - disabling pedantic mode and all warning doesn't remove it, as res supposed to be a returned constant, it have to be initialized.

Simplest way to suppress it, would be to add value initialization, which would have no cost for compile-time initialization:

    atype res {};

Also it looks like more idiomatic std::generate will be applicable here with C++20. Note - with C++17 your fill_array() isn't a constexpr due to returning atype res but you don't see a error because you allowed x to be runtime.

Proper get_array for C++17

static constexpr atype get_array()
{
    atype res {};
    for (std::size_t i = 0; i < size; i++)
        res[i] = 1;
    return res;
}
Swift - Friday Pie
  • 12,777
  • 2
  • 19
  • 42
  • Why is ```fill_array``` not constexpr? ```std::get``` is ```constexpr``` in c++14. – francesco Aug 12 '20 at 09:32
  • @francesco `res` is not a constant (obviously), passing value involves cast that wasn't allowed in constexpr expressions. And it cannot become constant because its members aren't initialized, it doesn't have any constructor (and if it had initializer list constructor, it wouldn't be a constexpr one). Just try declare x as constexpr and you'll see the difference (and godbolt will show code). If you fill `res` right in `get_array()` there will be no problem – Swift - Friday Pie Aug 12 '20 at 09:42