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

2

8

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?

Islamite answered 11/9, 2018 at 6:22 Comment(3)
Does it compile?Cris
This answer is for a different context, but covers returning std::initializer_list instances, too.Kreg
Damn, I totally forgot about transient nature of initializer_lists... Thanks @rafix07 and @songyuanyao!Islamite
G
9

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.

Gibbon answered 11/9, 2018 at 6:37 Comment(4)
But should this be the case in C++17 also with prvalue materialization? IOW does the conversion from the braced-init-list to the initializer_list happen in the return statement (and then a copy of the object is returned), or should the return value be directly initialized from the braced-init-list? In the latter case, it seems like the lifetime of the values should be extended along with that of the (original) initializer_list. Where is the flaw in my reasoning?Kisumu
@ArneVogel Because the initializer_list is the return value of a function, the lifetime of the temporary array is not extended, just like returning a reference to a local variable.Novokuznetsk
@Novokuznetsk Thanks, I think I get it now. Relevant std. quote: "The array has the same lifetime 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."1 – IOW, the initializer_list extends the lifetime of the temp. array, but only in contexts where a reference would also extend the lifetime of a temporary (i.e. when initializing a named variable at local, namespace or class-static scope, but not in a return).Kisumu
@ArneVogel Right, and there is a mistake in my previous reply... I meant "just like returning a reference to a temporary object".Novokuznetsk
G
3

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.

Grievance answered 11/9, 2018 at 6:37 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.