4

While asking another question recently, I stumbled upon some strange behavior of GCC when initializing a std::array with a parameter pack expansion followed by another element. I have already discussed this briefly with Jarod42 in the comments there but I believe it should better be asked as a new question.

For example, consider the following code that is supposed to provide a utility make_array function that takes an arbitrary number of parameters and std::forwards them to the std::array initialization. The leading tag parameter selects whether the array should be terminated with a default-constructed T (selected via std::true_type) or not (selected via std::false_type). I am then creating an integer array of either kind once with static and once with automatic storage duration. The array elements are finally printed out.

#include <array>        // std::array
#include <cstddef>      // std::size_t
#include <iomanip>      // std::setw
#include <ios>          // std::left, std::right
#include <iostream>     // std::cout
#include <string>       // std::string
#include <type_traits>  // std::false_type, std::true_type


// This is only used for visualization.
template <typename T, std::size_t N>
void
print_array(const std::string& name, const std::array<T, N>& arr)
{
  std::cout << std::setw(20) << std::left << (name + ":") << std::right << "{";
  for (auto iter = arr.cbegin(); iter != arr.cend(); ++iter)
    std::cout << (iter != arr.cbegin() ? ", " : "") << std::setw(2) << *iter;
  std::cout << "}\n";
}

// Create a `std::array<T>` with elements constructed from the provided
// arguments.
template <typename T, typename... ArgTs>
static constexpr auto
make_array(std::false_type, ArgTs&&... args) noexcept
{
  std::array<T, sizeof...(args)> values = { { std::forward<ArgTs>(args)... } };
  return values;
}

// Create a `std::array<T>` with elements constructed from the provided
// arguments followed by a default-constructed `T`.
template <typename T, typename... ArgTs>
static constexpr auto
make_array(std::true_type, ArgTs&&... args) noexcept
{
  std::array<T, sizeof...(args) + 1> values = {
    { std::forward<ArgTs>(args)..., T {} }
  };
  return values;
}

namespace /* anonymous */
{
  const auto values_no_static = make_array<int>(std::false_type(), 1, 2, 3, 4);
  const auto values_yes_static = make_array<int>(std::true_type(), 1, 2, 3, 4);
}

int
main()
{
  const auto values_no_automatic = make_array<int>(std::false_type(), 1, 2, 3, 4);
  const auto values_yes_automatic = make_array<int>(std::true_type(), 1, 2, 3, 4);
  print_array("static yes", values_yes_static);
  print_array("static no", values_no_static);
  print_array("automatic yes", values_yes_automatic);
  print_array("automatic no", values_no_automatic);
}

I expect to see the arrays {1, 2, 3, 4} and {1, 2, 3, 4, 0} printed two times. This is what Clang does. GCC, however, prints {0, 0, 0, 0, 0} for the terminated array with static storage duration. If I replace the trailing T {} in the initializer list with -1, I get {0, 0, 0, 0, -1} instead. So it seems that adding a trailing elements causes the parameter pack to expand to all zeros. But only if the resulting std::array has static storage duration.

Did I invoke undefined behavior or is this a bug in GCC? If it is undefined behavior, I'd be thankful for an official reference to the standard. I already know a simple way to work around this issue (see my answer to the question mentioned above) and am not interested in avoiding the problem. Rather, I want to know whether the code is well-formed and, if so, which compiler is right.

Complete output using GCC:

$ g++ --version
g++ (GCC) 5.1.0
Copyright (C) 2015 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
$
$ g++ -std=c++14 -o main_gcc -Wall -Wextra -Werror -pedantic main.cxx
$
$ ./main_gcc 
static yes:         { 0,  0,  0,  0,  0}
static no:          { 1,  2,  3,  4}
automatic yes:      { 1,  2,  3,  4,  0}
automatic no:       { 1,  2,  3,  4}

Using Clang:

$ clang --version
clang version 3.6.2 (tags/RELEASE_362/final)
Target: x86_64-unknown-linux-gnu
Thread model: posix
$
$ clang -std=c++14 -o main_clang -Wall -Wextra -Werror -pedantic main.cxx -lstdc++
$
$ ./main_clang 
static yes:         { 1,  2,  3,  4,  0}
static no:          { 1,  2,  3,  4}
automatic yes:      { 1,  2,  3,  4,  0}
automatic no:       { 1,  2,  3,  4}
5gon12eder
  • 24,280
  • 5
  • 45
  • 92

2 Answers2

3

Minimal reproducing sample:

#include <array>

constexpr auto make_array(int i)
{
    std::array<int, 2> values = { i, 0 };
    return values;
}

constexpr auto arr = make_array(1);

static_assert(arr[0] == 1, "");

Demo with HEAD. By now we can safely say that no UB is induced.

Note how we can work around the bug:

Columbo
  • 60,038
  • 8
  • 155
  • 203
1

I have reported this to the GCC developers. It was recognized as bug 67104 and fixed in August 2015 for GCC 6 and back-ported to GCC 5.3. You can use the link to the online compiler provided in Columbo's answer to check.

5gon12eder
  • 24,280
  • 5
  • 45
  • 92