基于范围的支持初始化器超过非const值吗?

时间:2015-07-30 13:14:53

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

我正在尝试迭代多个var temp = new MyClass[] { new MyClass { Name = "object1", Array = new int[] { 1, 2, 3 } }, new MyClass { Name = "object2", Array = new int[] { 1, 2 } }, new MyClass { Name = "object3", Array = new int[] { 1, 2 } }, new MyClass { Name = "object4", Array =null } }; var result = temp.GroupBy(i => i.Array, new ArrayComparer()).ToList(); //Now you have 3 groups ,对每个std::list进行排序。这是天真的方法:

#include<list>
using namespace std;
int main(void){
    list<int> a,b,c;
    for(auto& l:{a,b,c}) l.sort();
}
制造

aa.cpp:5:25: error: no matching member function for call to 'sort'
        for(auto& l:{a,b,c}) l.sort();
                             ~~^~~~
/usr/bin/../lib64/gcc/x86_64-linux-gnu/4.9/../../../../include/c++/4.9/bits/stl_list.h:1586:7: note: 
      candidate function not viable: 'this' argument has type 'const
      std::list<int, std::allocator<int> >', but method is not marked const
      sort();
      ^
/usr/bin/../lib64/gcc/x86_64-linux-gnu/4.9/../../../../include/c++/4.9/bits/stl_list.h:1596:9: note: 
      candidate function template not viable: requires 1 argument, but 0 were
      provided
        sort(_StrictWeakOrdering);
        ^
1 error generated.

我是否正确地猜测大括号初始化程序正在创建这些列表的副本?有没有办法不复制它们,并使它们在循环中可修改? (除了制作指向它们的指针列表,这是我当前的解决方法)。

5 个答案:

答案 0 :(得分:23)

你猜对了。 std::initializer_list元素始终为const(这使sort()不可能,因为sort()是非const成员函数,并且其元素始终被复制(这会使sort() - 即使它们不是const,也毫无意义。来自[dcl.init.list],强调我的:

  

类型为std::initializer_list<E>的对象是从初始化列表构造的,就像实现一样   分配了一个N个元素的临时数组,类型为 const E ,其中N是元素的数量   初始化列表。该数组的每个元素都使用初始化程序的相应元素进行复制初始化   列表,构造std::initializer_list<E>对象以引用该数组。 [注意:一个构造函数   或者,在初始化程序的上下文中,可以访问为副本选择的转换函数(第11条)   名单。 -end note] 如果需要缩小转换来初始化任何元素,程序是   病态的。 [例如:

struct X {
    X(std::initializer_list<double> v);
};
X x{ 1,2,3 };
     

初始化将以与此大致相同的方式实现:

const double __a[3] = {double{1}, double{2}, double{3}};
X x(std::initializer_list<double>(__a, __a+3));
     

假设实现可以使用一对指针构造initializer_list对象。 末端   例子]

无法使它们成为非常量或非复制的。指针解决方案有效:

for (auto l : {&a, &b, &c}) l->sort();

因为它是指针是const,而不是它指向的元素。另一种选择是编写一个可变参数函数模板:

template <typename... Lists>
void sortAll(Lists&&... lists) {
    using expander = int[];
    expander{0, (void(lists.sort()), 0)...};
}

sortAll(a, b, c);

我猜你也可以写一个帮助器将你的列表包装成reference_wrapperlist<int>的数组(因为你不能有一个引用数组),但这可能更多令人困惑而不是有用:

template <typename List, typename... Lists>
std::array<std::reference_wrapper<List>, sizeof...(Lists) + 1>
as_array(List& x, Lists&... xs) {
    return {x, xs...}; 
}

for (list<int>& l : as_array(a, b, c)) {  // can't use auto, that deduces
    l.sort();                             // reference_wrapper<list<int>>,
}                                         // so would need l.get().sort()

答案 1 :(得分:7)

可以编写一个允许您执行此操作的函数ref_range

for(auto& l : ref_range(a,b,c)) {
    l.sort();
}

正如其他人所说,一旦你写{a,b,c},你就会被initializer_list所困,而这样的列表总是会复制其参数。副本为const(因此您的错误),但即使您可以获得非const引用,您也会修改ab和{{ 1}}而不是原件。

无论如何,这里是c。它构建ref_range vector。{/ p>

reference_wrapper

答案 2 :(得分:2)

{...}语法实际上是在创建std::initializer_list。如链接页面所述:

  

在以下情况下自动构建std::initializer_list对象:

     
      
  • [...]
  •   
  • braced-init-list 绑定到auto,包括在一个范围内的for循环中
  •   

并且:

  

std::initializer_list<T>类型的对象是一个轻量级代理对象,它提供对const T类型对象数组的访问。

因此,您无法修改通过此initialize_list访问的对象。使用指针的解决方案看起来对我来说是最简单的。

答案 3 :(得分:2)

直接回答您的问题:

  

我是否正确地猜测大括号初始化程序正在创建副本   那些名单?

是的,这是第一个问题。您的代码将创建列表的副本,对这些副本进行排序,最后忘记已排序的副本。

然而,仅此一项就会导致无效的代码。编译器错误提示第二个问题:隐式类型llist<int> const&,而不是list<int>&。所以编译器抱怨sort()试图修改常量列表。

您可以使用讨厌的const_cast

解决第二个问题
#include <list>
#include <iostream>
using namespace std;
int main(void){
    list<int> a,b,c;
    a.push_back(2);
    a.push_back(0);
    a.push_back(1);
    for(auto& l:{a,b,c}) const_cast<list<int>&>(l).sort();
    for(auto i:a) cout << i << endl;
}

然而,这将触发第一个问题:您的列表列表包含副本,并且只对这些副本进行排序。所以最终输出不是你想要的:

2
0
1

最简单的解决方法是创建指向列表的指针列表:

#include <list>
#include <iostream>
using namespace std;
int main(void){
    list<int> a,b,c;
    a.push_back(2);
    a.push_back(0);
    a.push_back(1);
    for(auto l:{&a,&b,&c}) l->sort();
    for(auto i:a) cout << i << endl;
}

这将产生所需的结果:

0
1
2

答案 4 :(得分:0)

其他人已经提到了 ffmpeg-N-*,但他们随后使用它来创建 STL 容器,而不是坚持使用大括号初始化器列表。

所以你需要做的就是:

std::reference_wrapper

当然,这与已经提出的指针解决方案非常相似。