在一个临时std::initializer_list上迭代,范围基于for

Iterating over a temporary std::initializer_list with range based for

本文关键字:迭代 list 范围 for initializer 一个 std      更新时间:2023-10-16

给定以下代码

#include <iostream>
#include <initializer_list>
#include <string>
int a, b;
int main() {
    for (auto p : std::initializer_list<std::pair<int &, std::string>>{
        { a, "a" },
        { b, "b" },
    })
    {
        std::cout << p.second << ": " << p.first << 'n';
    }
}

我期望输出

a: 0
b: 0

和gcc和clang同意,然而,Visual Studio 2013 Update 5(我的版本,不确定rextester使用什么)不同意并打印如下:

: 0
: 0

Visual Studio有一个std::initializer_list的问题,但它应该已经修复自更新2。

这是一个错误在Visual Studio或我调用未定义或未指定的行为?

在cppreference的"Explanation"一节中,range-for循环相当于:

{
    auto && __range = range_expression ;
    for (auto __begin = begin_expr,
                __end = end_expr;
            __begin != __end;
            ++__begin) {
        range_declaration = *__begin;
        loop_statement
    }
}

可以看到,迭代范围(range_epxression)被绑定到一个转发引用。在你的例子中,后者变成了一个右值引用,因为你的initializer_list是一个临时的,它的生命周期因此被扩展到整个for循环。

from cppreference.com:

底层数组不保证在初始化列表对象的生命周期结束后仍然存在。std::initializer_list的存储是未指定的(也就是说,根据具体情况,它可以是自动、临时或静态只读内存)。(直到c++ 14)

底层数组是一个临时数组,其中的每个元素都是从原始初始化列表的相应元素复制初始化的(除非窄化转换无效)。底层数组的生命周期与任何其他临时对象相同,只是从数组初始化initializer_list对象会扩展数组的生命周期,就像将引用绑定到临时对象一样(有相同的例外,例如初始化非静态类成员)。底层数组可以在只读内存中分配。(因为c++ 14)

从这里我可以推断出编译器出错了,因为initializer_list的生命周期应该在右括号之后结束。