lambda返回initializer_list中的奇怪值

时间:2015-02-04 10:17:54

标签: c++ c++11 lambda initializer-list

考虑这个 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;
}

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

clang 3.5 输出:

-1078533848 -1078533752 134518134

gcc 4.8.2 输出:

-1078845996 -1078845984 3

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

1 2 267998238

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

-1785083736 0 6297428 

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

请帮我解开这个谜。

2 个答案:

答案 0 :(得分:32)

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

  

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

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

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

  

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

答案 1 :(得分:2)

因此,当initializer_list自身被复制或移动到复制/移动的结果时,template<size_t size, class T> std::array<T, size> partial_array( T const* begin, T const* end ) { std::array<T, size> retval; size_t delta = (std::min)( size, end-begin ); end = begin+delta; std::copy( begin, end, retval.begin() ); return retval; } template<class T, size_t max_size> struct capped_array { std::array<T, max_size> storage; size_t used = 0; template<size_t osize, class=std::enable_if_t< (size<=max_size) >> capped_array( std::array<T, osize> const& rhs ): capped_array( rhs.data(), rhs.data()+osize ) {} template<size_t osize, class=std::enable_if_t< (size<=max_size) >> capped_array( capped_array<T, osize> const& rhs ): capped_array( rhs.data(), rhs.data()+rhs.used ) {} capped_array(capped_array const& o)=default; capped_array(capped_array & o)=default; capped_array(capped_array && o)=default; capped_array(capped_array const&& o)=default; capped_array& operator=(capped_array const& o)=default; capped_array& operator=(capped_array & o)=default; capped_array& operator=(capped_array && o)=default; capped_array& operator=(capped_array const&& o)=default; // finish-start MUST be less than max_size, or we will truncate capped_array( T const* start, T const* finish ): storage( partial_array(start, finish) ), used((std::min)(finish-start, size)) {} T* begin() { return storage.data(); } T* end() { return storage.data()+used; } T const* begin() const { return storage.data(); } T const* end() const { return storage.data()+used; } size_t size() const { return used; } bool empty() const { return !used; } T& front() { return *begin(); } T const& front() const { return *begin(); } T& back() { return *std::prev(end()); } T const& back() const { return *std::prev(end()); } capped_array( std::initializer_list<T> il ): capped_array(il.begin(), il.end() ) {} }; s不会延长其引用数组的生命周期。这使得返回它们成为问题。 (它们确实将引用数组的生命周期延长到它们自己的生命周期,但是这个扩展不能通过elision或列表副本传递。)

要解决此问题,请存储数据并手动管理其生命周期:

T

这里的目标很简单。创建一个基于堆栈的数据类型,该类型存储一堆std::initializer_list,最多可达一个上限,并且可以处理更少的数据。

现在我们将您的auto get_foos = [=] (Switch ss) -> capped_array<int,3> { 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"); } }; 替换为:

T

并且您的代码有效。不使用免费商店(没有堆分配)。

更高级的版本将使用未初始化数据的数组并手动构建每个{{1}}。