递归使用时 std::initializer_list 的生命周期

Lifetime of std::initializer_list when used recursively

我正在尝试使用 std::initializer_list 来定义和输出递归数据结构。在下面的示例中,我正在处理一个列表,其中每个元素可以是整数或同一类型列表的另一个实例。我使用中间变体类型执行此操作,它可以是初始化列表或整数。

我不清楚 std::initializer_list 的生命周期是否足以支持这个用例,或者我是否会遇到内存访问不一致的可能性。代码运行良好,但我担心这不能保证。我希望用于创建最顶层列表的 std::initializer_list 和任何中间、临时 std::initializer_list 对象仅在顶层表达式完成后才被清除。

struct wrapped {
    bool has_list;
    int n = 0;
    std::initializer_list<wrapped> lst;

    wrapped(int n_) : has_list(false), n(n_) {}
    wrapped(std::initializer_list<wrapped> lst_) : has_list(true), lst(lst_) {}

    void output() const {
      if (!has_list) {
        std::cout << n << ' ';
      } else {
        std::cout << "[ ";
        for (auto&& x : lst)  x.output();
        std::cout << "] ";
      }
    }
  };

  void call_it(wrapped w) {
    w.output();
    std::cout << std::endl;
  }

  void call_it() {
    call_it({1});                 // [ 1 ]
    call_it({1,2, {3,4}, 5});     // [ 1 2 [ 3 4 ] 5 ]
    call_it({1,{{{{2}}}}});       // [ 1 [ [ [ [ 2 ] ] ] ] ]
  }

这是安全的还是未定义的行为?

你所做的应该是安全的。 ISO 标准第 6.7.7 节第 6 段描述了第三个也是最后一个上下文,其中临时变量在与完整表达式末尾不同的点被销毁。脚注 (35) 明确指出这适用于 intializer_list 及其底层临时数组的初始化:

The temporary object to which the reference is bound or the temporary object that is the complete object of a subobject to which the reference is bound persists for the lifetime of the reference if the glvalue to which the reference is bound was obtained through one of the following: ...

...(其中,根据 7.2.1,glvalue 是一个表达式,其计算确定对象、bit-field 或函数的身份),然后枚举条件。 (6.9)中特别提到:

A temporary object bound to a reference parameter in a function call (7.6.1.2) persists until the completion of the full-expression containing the call.

正如我所读,这保护了构建对 call_it 的顶部调用的最终参数所需的一切,这正是您想要的。

据我所知程序有未定义的行为。

成员声明 std::initializer_list<wrapped> lst; 要求类型完整,因此将隐式实例化 std::initializer_list<wrapped>

此时wrapped是一个不完整的类型。根据[res.on.functions]/2.5,如果没有声明特定的异常,实例化一个不完整类型的标准库模板作为模板参数是未定义的。

我在 [support.initlist] 中没有看到任何此类异常。

另见活动 LWG issue 2493