可变模板参数类型列表的打包扩展到复合类型的初始化列表中——这样做是否合法?

8
我希望能够将一个可变类型列表“实体化”为相关值的initializer_list。 例如,将包含多个std::integral_constant的std::tuple转换为std::initializer_list{...}。 一般情况下,我想要获取某些复杂类型的initializer_list,例如std::string。
但是,以下简单示例在Clang编译时会导致崩溃(尽管在Coliru上使用GCC可以正常工作),因此我怀疑存在未定义行为(或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";
   }
}

在Coliru上实时运行

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


它能编译吗? - doron
这个答案是用于不同上下文的,但也涵盖了返回 std::initializer_list 实例。 - lubgr
该死,我完全忘记了初始化列表的瞬态本质... 感谢 @rafix07 和 @songyuanyao! - Alexander Morozov
2个回答

9

关于initializer_list,有两点需要注意:

初始化列表可以被实现为一对指针或指针和长度。复制std::initializer_list不会复制底层对象

以及

在原始的初始化列表对象的生命周期结束后,底层数组不能保证存在。std::initializer_list的存储是未指定的(即它可能是自动的、临时的或静态的只读内存,具体取决于情况)。

因此,在这行代码中:

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

你正在创建本地数组,initializer_list会保留指向该数组开头/结尾的指针,当函数超出作用域时,你将拥有悬空指针。


但是在 C++17 中使用 prvalue materialization,这种情况是否也适用?也就是说,从 花括号初始化列表initializer_list 的转换是否发生在 return 语句中(然后返回对象的副本),还是返回值应该直接从 花括号初始化列表 初始化?在后一种情况下,似乎值的生命周期应随 (原始) initializer_list 的生命周期延长。我的推理中有什么缺陷? - Arne Vogel
1
由于initializer_list是函数的返回值,临时数组的生命周期没有被延长,就像返回本地变量的引用一样。 - xskxzr
@xskxzr 谢谢,我现在明白了。相关的标准引用:“该数组的生命周期与任何其他临时对象相同,除了从该数组初始化initializer_list对象会像绑定到临时对象的引用一样延长数组的生命周期。”[1](https://timsong-cpp.github.io/cppwp/dcl.init.list#6)- 换句话说,initializer_list仅在引用也会延长临时对象的生命周期的情况下扩展临时数组的生命周期(即在本地、命名空间或类静态范围内初始化命名变量时,但不适用于return)。 - Arne Vogel
1
@ArneVogel 对的,在我之前的回复中有一个错误... 我的意思是“就像返回对临时对象的引用一样”。 - xskxzr

3

std::initializer_list 的底层数组实际上是一个本地临时对象。当退出 materialize 时,它已被销毁。复制 std::initializer_list 不会复制底层数组,返回的 std::initializer_list 的内容始终无效,尝试访问返回的 std::initializer_list 的内容会导致 UB。

(强调是我的)

初始化列表可以实现为指针对或指针和长度对。 复制 std::initializer_list 不会复制底层对象

底层数组是一个临时数组,类型为 const T[N],其中每个元素从原始初始化列表的相应元素进行复制初始化(除了缩小转换无效)。底层数组的生命周期与任何其他临时对象相同,除了从数组初始化 initializer_list 对象会延长数组的生命周期,就像将引用绑定到临时对象一样(有相同的例外,例如初始化非静态类成员)。底层数组可能分配在只读内存中。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接