将可变类型列表的扩展打包到复杂类型的初始化列表中——这合法吗?

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

我想 "materialize" 可变类型列表到 initializer_list 相关值。 例如,有几个 std::integral_constant<T, x>std::tuple 得到 std::initializer_list<T>{...}。 在一般情况下,我想得到一些复杂类型的 initializer_list,比如 std::string.

但是下面这个简单的例子让我在用 Clang 编译时崩溃了(尽管它适用于 GCC,至少在 Coliru 上是这样),所以我怀疑是 UB(或 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

那么,这样的代码合法吗?在 C++11/14/17 中?

std::initializer_list的底层数组其实是一个本地临时对象。从materialize出来的时候已经被摧毁了。复制一个 std::initializer_list 不会复制底层数组,返回的 std::initializer_list 的内容总是无效的并且试图访问返回的 std::initializer_list 的内容会导致 UB.

(强调我的)

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.

关于 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.

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).

所以在这一行中

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

您正在创建本地数组,initializer_list 保持指向此数组开头/结尾的指针,当函数超出范围时,您有悬空指针。