Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Pack expansion of variadic list of types into initializer list of complex types - is it legal?

I would like to "materialize" a variadic types list into an initializer_list of related values. For example, having an std::tuple of several std::integral_constant<T, x> get an std::initializer_list<T>{...}. In general case, I would like to get initializer_list of some complex type, like std::string.

But the following simple example gives me a crash when compiled by Clang (although it works with GCC, at least on Coliru), so I suspect UB (or bug in Clang):

template <class... Ts>
std::initializer_list<const std::string> materialize()
{
    return {
      std::to_string(Ts::value)...
    };
}

void print_out()
{
   for (const auto & x : materialize<std::true_type, std::false_type>()) {
      std::cout << x << "\n";
   }
}

Live on Coliru

So, is such code legal? In C++11/14/17?

like image 793
Alexander Morozov Avatar asked Sep 11 '18 06:09

Alexander Morozov


2 Answers

Two things about initializer_list:

Initializer lists may be implemented as a pair of pointers or pointer and length. Copying a std::initializer_list does not copy the underlying objects.

and

The underlying array is not guaranteed to exist after the lifetime of the original initializer list object has ended. The storage for std::initializer_list is unspecified (i.e. it could be automatic, temporary, or static read-only memory, depending on the situation).

so in this line

return {
      std::to_string(Ts::value)...
    };

you are creating local array, initializer_list keeps pointer to the beginning / end of this array, when function goes out of scope you have dangling pointers.

like image 128
rafix07 Avatar answered Oct 18 '22 23:10

rafix07


The underlying array of std::initializer_list is a local temporary object in fact. When get out of materialize it has been destroyed. Copying an std::initializer_list doesn't copy the underlying array, the content of the returned std::initializer_list is always invalid and trying to access the content of the returned std::initializer_list leads to UB.

(emphasis mine)

Initializer lists may be implemented as a pair of pointers or pointer and length. Copying a std::initializer_list does not copy the underlying objects.

The underlying array is a temporary array of type const T[N], in which each element is copy-initialized (except that narrowing conversions are invalid) from the corresponding element of the original initializer list. 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). The underlying array may be allocated in read-only memory.

like image 3
songyuanyao Avatar answered Oct 19 '22 00:10

songyuanyao