提问者:小点点

在非常量值上使用大括号初始值设定项的基于范围的for?


我试图迭代许多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.

我猜对了吗?大括号初始值设定项正在创建那些列表的副本? 有没有一种方法可以不复制它们,并使它们在循环中可以修改? (除了列出指向它们的指针列表,这是我目前的工作方法)。


共3个答案

匿名用户

你猜对了。 std::initializer_list元素始终是常量(这使得sort()无法对它们进行操作,因为sort()是一个非常量成员函数),并且它的元素始终被复制(这使得sort()-即使它们不是常量,它们也没有意义)。 从[dcl.init.list],强调mine:

类型为std::initializer_list的对象是从初始值设定项列表构造的,就好像实现分配了一个由N个const E类型元素组成的临时数组,其中N是初始值设定项列表中的元素数。 该数组的每个元素都用初始值设定项列表的相应元素进行复制初始化,并构造std::initializer_list对象来引用该数组。 [注:为副本选择的构造函数或转换函数应可在初始值列表的上下文中访问(第11条)。-结束注]如果需要窄化转换来初始化任何元素,则程序的格式不正确。 [示例:

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的数组(因为您不能有引用数组),但这可能更容易混淆,而不是帮助:

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()

匿名用户

可以编写一个函数ref_range来执行以下操作:

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

正如其他人所说的那样,一旦您编写了{a,b,c},您就会遇到一个initializer_list,而这样的列表总是获取其参数的副本。 副本是const(因此出现了错误),但是即使您可以获得非const引用,您也将修改abc的副本,而不是原始副本。

总之,下面是ref_range。 它构建reference_wrapper向量

// http://stackoverflow.com/questions/31724863/range-based-for-with-brace-initializer-over-non-const-values
#include<list>
#include<functional>
#include<array>

template<typename T, std:: size_t N>
struct hold_array_of_refs {
    using vec_type = std:: array< std:: reference_wrapper<T>, N >;
    vec_type m_v_of_refs;
    hold_array_of_refs(vec_type && v_of_refs) : m_v_of_refs(std::move(v_of_refs)) { }
    ~hold_array_of_refs() { }
    struct iterator {
        typename vec_type :: const_iterator m_it;
        iterator(typename vec_type :: const_iterator it) : m_it(it) {}
        bool operator != (const iterator &other) {
            return this->m_it != other.m_it;
        }
        iterator& operator++() { // prefix
            ++ this->m_it;
            return *this;
        }
        T& operator*() {
            return *m_it;
        }
    };

    iterator begin() const {
        return iterator(m_v_of_refs.begin());
    }
    iterator end() const {
        return iterator(m_v_of_refs.end());
    }
};

template<typename... Ts>
using getFirstTypeOfPack = typename std::tuple_element<0, std::tuple<Ts...>>::type;


template<typename ...T>
auto ref_range(T&... args) -> hold_array_of_refs< getFirstTypeOfPack<T...> , sizeof...(args)> {
    return {{{ std:: ref(args)... }}}; // Why does clang prefer three levels of {} ?
}

#include<iostream>
int main(void){
    std:: list<int> a,b,c;
    // print the addresses, so we can verify we're dealing
    // with the same objects
    std:: cout << &a << std:: endl;
    std:: cout << &b << std:: endl;
    std:: cout << &c << std:: endl;
    for(auto& l : ref_range(a,b,c)) {
        std:: cout << &l << std:: endl;
        l.sort();
    }
}

匿名用户

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

在下列情况下,将自动构造std::initializer_list对象:

  • [...]
  • 带括号的init-list绑定到auto,包括在ranged for循环中

和:

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

因此,您不能修改通过此initialize_list访问的对象。 对我来说,你的解决方案看起来是最简单的。