lambda返回initializer_list中的奇怪值

Gre*_*ape 33 c++ lambda initializer-list c++11

考虑一下这个C++ 11代码片段:

#include <iostream>
#include <set>
#include <stdexcept>
#include <initializer_list>


int main(int argc, char ** argv)
{
    enum Switch {
        Switch_1,
        Switch_2,
        Switch_3,
        Switch_XXXX,
    };

    int foo_1 = 1;
    int foo_2 = 2;
    int foo_3 = 3;
    int foo_4 = 4;
    int foo_5 = 5;
    int foo_6 = 6;
    int foo_7 = 7;

    auto get_foos = [=] (Switch ss) -> std::initializer_list<int> {
        switch (ss) {
            case Switch_1:
                return {foo_1, foo_2, foo_3};
            case Switch_2:
                return {foo_4, foo_5};
            case Switch_3:
                return {foo_6, foo_7};
            default:
                throw std::logic_error("invalid switch");
        }
    };

    std::set<int> foos = get_foos(Switch_1);
    for (auto && foo : foos) {
        std::cout << foo << " ";
    }
    std::cout << std::endl;
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

无论我尝试什么编译器,所有人似乎都错误地处理它.这让我觉得我做错了,而不是多个编译器的常见错误.

铿锵3.5输出:

-1078533848 -1078533752 134518134
Run Code Online (Sandbox Code Playgroud)

gcc 4.8.2输出:

-1078845996 -1078845984 3
Run Code Online (Sandbox Code Playgroud)

gcc 4.8.3输出(在http://www.tutorialspoint.com上编译):

1 2 267998238
Run Code Online (Sandbox Code Playgroud)

gcc(未知版本)输出(在http://coliru.stacked-crooked.com上编译)

-1785083736 0 6297428 
Run Code Online (Sandbox Code Playgroud)

这个问题似乎是由于使用std::initializer_list<int>lambda的返回值引起的.将lambda定义更改为[=] (Switch ss) -> std::set<int> {...}返回值时是正确的.

拜托,帮我解开这个谜.

haa*_*vee 32

来自:http://en.cppreference.com/w/cpp/utility/initializer_list

在原始初始化程序列表对象的生存期结束后,不保证基础数组存在.std :: initializer_list的存储是未指定的(即它可以是自动,临时或静态只读存储器,具体取决于具体情况).

我不认为初始化列表是可复制构造的.std::set和其他容器.基本上,您的代码看起来类似于"返回对临时的引用".

C++ 14与底层存储有一些不同之处 - 延长它的生命周期 - 但这并不能解决任何与initializer_list对象生命周期有关的问题,更不用说它的副本了.因此,即使在C++ 14中,问题仍然存在.

底层数组是一个临时数组,其中每个元素都是从原始初始化列表的相应元素进行复制初始化(除了缩小转换无效).底层数组的生命周期与任何其他临时对象相同,除了从数组初始化initializer_list对象扩展了数组的生命周期,就像绑定对临时的引用一样(具有相同的例外,例如初始化非 - 静态班级成员).底层数组可以在只读存储器中分配.

  • 是的,这正是发生的事情.init列表由堆栈分配的数组支持,当lambda返回时,该数组变为poof. (4认同)
  • `initializer_list`是可复制的(因此编译)但它只执行浅拷贝.坦率地说,我发现这是一个糟糕的C++ 11"功能".幸运的是,这是在C++ 14中修复的,其中底层数组的生命周期在`initializer_list`的副本期间被扩展,就像将它绑定到引用一样._Unfortunately_,GCC 4.9.2在C++ 14模式[仍然得到它错](http://coliru.stacked-crooked.com/a/b8c5cff3eefab374).我没有用HEAD测试过. (4认同)
  • *"幸运的是,这个'监督'可能/应该已经修复了C++ 14"*,你粘贴的段落的句子表明这个*应该*固定,这是*监督*?:*"生命周期底层数组的**与任何其他临时对象**相同,只是从数组初始化initializer_list对象会延长数组的生命周期,就像绑定对临时**"*的引用一样.创建由其他引用类型变量初始化的引用不会延长原始临时的生存期,直到*last*引用存在.数组是暂时的 (4认同)
  • @LightnessRacesinOrbit数组的生命周期延长到`initializer_list`对象的生命周期,用于初始化结束; 但是`initializer_list`对象是lambda的临时返回值,其生命周期以`;`结尾.(这甚至不计算问题中的数组在return语句中"绑定"的事实,所以通常你根本不会得到任何生命周期扩展.) (4认同)

Sha*_*our 15

问题是您正在引用一个不再存在的对象,因此您正在调用未定义的行为.initializer_listC++ 11草案标准中似乎没有明确规定,没有规范的部分实际指定了这种行为.虽然有很多笔记表明这不起作用,但总的来说,如果笔记不与规范性文本冲突,则它们不具有规范性,但它们具有很强的指示性.

如果我们转到18.9 初始化列表部分,它会有一条说明:

复制初始化列表不会复制基础元素.

在本节中,8.5.4我们有以下示例:

typedef std::complex<double> cmplx;
std::vector<cmplx> v1 = { 1, 2, 3 };

void f() {
    std::vector<cmplx> v2{ 1, 2, 3 };
    std::initializer_list<int> i3 = { 1, 2, 3 };
}
Run Code Online (Sandbox Code Playgroud)

以下注释:

对于v1和v2,为{1,2,3}创建的initializer_list对象和数组具有完整表达式生存期.对于i3,initializer_list对象和数组具有自动生命周期.

这些注释与initializer_list提议一致:N2215给出了以下示例:

std::vector<double> v = {1, 2, 3.14};
Run Code Online (Sandbox Code Playgroud)

并说:

现在添加vector(initializer_list<E>)vector<E>如上所示.现在,该示例有效.初始化列表{1,2,3.14}被解释为临时构造如下:

const double temp[] = {double(1), double(2), 3.14 } ;
initializer_list<double> tmp(temp,
sizeof(temp)/sizeof(double));
vector<double> v(tmp);
Run Code Online (Sandbox Code Playgroud)

[...]

请注意,initializer_list是一个小对象(可能是两个单词),因此按值传递它是有意义的.通过值传递还简化了begin()和end()的内联以及size()的常量表达式求值.

初始化器列表将由编译器创建,但可由用户复制.把它想象成一对指针.

initializer_list这种情况下只是保持指针,这将不离开后范围存在自动变量.

更新

我刚刚意识到该提案实际上指出了这种滥用情况:

一个含义是,initializer_list是"指针式",因为它的行为类似于底层数组的指针.例如:

int * f(int a)
{ 
   int* p = &a;
   return p; //bug waiting to happen
}

initializer_list<int> g(int a, int b, int c)
{
   initializer_list<int> v = { a, b, c };
   return v; // bug waiting to happen
} 
Run Code Online (Sandbox Code Playgroud)

以这种方式滥用initializer_list实际上需要少量的聪明才智.特别是,initializer_list类型的变量很少见.

我发现最后一句话(强调我的)特别具有讽刺意味.

更新2

因此,缺陷报告1290修复了规范性措辞,因此它现在涵盖了这种行为,尽管副本案例可能更加明确.它说:

当initializer_list是类的非静态数据成员时,会出现关于预期行为的问题.initializer_list的初始化是根据隐式分配的数组的结构来定义的,该数组的生存期"与initializer_list对象的生命周期相同".这意味着只要initializer_list执行就需要生成数组,这表面上看起来需要将数组存储在同一类中的std :: unique_ptr之类的内容中(如果成员在这种方式).

如果这是意图,那将是令人惊讶的,但它会使initializer_list在此上下文中可用.

该决议修正了措辞,我们可以在标准草案N3485版本中找到新的措辞.所以8.5.4 [dcl.init.list]部分现在说:

该数组与任何其他临时对象(12.2)具有相同的生命周期,除了从数组初始化initializer_- list对象延长了数组的生命周期,就像绑定对临时对象的引用一样.

12.2 [class.temporary]说:

函数返回语句(6.6.3)中返回值临时绑定的生命周期未扩展; 临时在return语句中的full-expression结束时被销毁.