11

The following program compiles without warnings in GCC and Clang and produces the expected output:

#include <initializer_list>
#include <iostream>

constexpr std::initializer_list<std::initializer_list<const char*>> list = {
    {"a", "b", "c"},
    {"d"}
};

int main() {
    for (const auto& outer: list) {
        std::cout << "level:\n";
        for (const auto& inner: outer) {
            std::cout << "  " << inner << "\n";
        }
    }
}

Using MSVC however, the program does not produce any output at all.

Is this program valid according to the C++ standard? Is this a bug in MSVC? If this is not valid C++ then why is there no warning from GCC or Clang? Is there a better way to create a constexpr nested list where the inner list does not have a fixed size?

cigien
  • 57,834
  • 11
  • 73
  • 112
eyelash
  • 3,197
  • 25
  • 33
  • 2
    When running that, it returns `STATUS_ACCESS_VIOLATION` in MSVC... I might be wrong, but I'd actually think this is UB. Since the initializer list is being looped over at run time, how could the addresses at compile time still be valid? (guessing) – ChrisMM Oct 26 '22 at 14:14
  • 1
    Compiler differences - live - https://godbolt.org/z/WG5Mevnox - +1 – Richard Critten Oct 26 '22 at 14:20
  • 1
    https://en.cppreference.com/w/cpp/utility/initializer_list, officially initializer lists are temporary objects only (The underlying array is a temporary array of type const T[N]) https://en.cppreference.com/w/cpp/utility/initializer_list AFAIK initializer lists can only safely be used in a temporary context (e.g. pass to a constructor). Disclaimer I haven't looked at what the standard exact wording is. – Pepijn Kramer Oct 26 '22 at 14:22
  • @PepijnKramer You are partially correct, further in your link there is: *The lifetime of the underlying array is the same as any other temporary object, except that initializing an initializer_list object from the array extends the lifetime of the array exactly like binding a reference to a temporary (with the same exceptions, such as for initializing a non-static class member).* – NathanOliver Oct 26 '22 at 14:23
  • No idea what the current status is, but it seems initializer_lists have a history ;) https://stackoverflow.com/questions/16063123/is-it-legal-to-declare-a-constexpr-initializer-list-object, https://stackoverflow.com/questions/27496004/why-isnt-stdinitializer-list-defined-as-a-literal-type. Anyway reading all this doesn't change my mental model yet that I cannot use std::initializer lists as variables (constexpr or not). Would be nice if someone actually knew :) – Pepijn Kramer Oct 26 '22 at 14:27

1 Answers1

0

[dcl.init.list]/6 (from C++20 draft N4860) states that

The array has the same lifetime as any other temporary object (6.7.7), except that initializing an initializer_list object from the array extends the lifetime of the array exactly like binding a reference to a temporary.

They include these examples:

void f() {
   std::vector<cmplx> v2{ 1, 2, 3 };
   std::initializer_list<int> i3 = { 1, 2, 3 };
}

struct A {
    std::initializer_list<int> i4;
    A() : i4{ 1, 2, 3 } {} // ill-formed, would create a dangling reference
};

The standard continues, in the same paragraph, with

For v1 and v2, the initializer_list object is a parameter in a function call, so the array created for { 1, 2, 3 } has full-expression lifetime. For i3, the initializer_list object is a variable, so the array persists for the lifetime of the variable. For i4, the initializer_list object is initialized in the constructor's ctor-initializer as if by binding a temporary array to a reference member, so the program is ill-formed (11.10.2).

(emphasis mine)

In your example, I believe, it is equivalent to example i3; thus the use of the initializer is valid. However, the constexpr, I think, is what is causing the issue. If you remove constexpr, MSVC, g++, and clang are all happy and execute the code.

I might be wrong, but I'd actually think this is a bug in MSVC. When running the code, MSVC exits with STATUS_ACCESS_VIOLATION. I'm assuming this is because the addresses are no longer valid that it's trying to reference when printing -- using int instead of const char* consistently printed 0 for me, though I'd expect that to be more random like access uninitialized memory.

ChrisMM
  • 8,448
  • 13
  • 29
  • 48