简体   繁体   English

Initializer-list-构造不可复制(但可移动)对象的向量

[英]Initializer-list-constructing a vector of noncopyable (but movable) objects

One can push_back rvalues of a noncopyable-but-movable type into a vector of that type: 可以将不可复制但可移动类型的push_back rvalues转换为该类型的向量:

#include <vector>

struct S
{
    S(int);
    S(S&&);
};

int main()
{
    std::vector<S> v;
    v.push_back(S(1));
    v.push_back(S(2));
    v.push_back(S(3));
}

However, when I try to initializer-list-construct the vector with the same rvalues, I get errors about a copy constructor being required: 但是,当我尝试初始化列表构造具有相同rvalues的向量时,我得到关于所需的复制构造函数的错误:

#include <vector>

struct S
{
    S(int);
    S(S&&);
};

int main()
{
    std::vector<S> v = {S(1), S(2), S(3)};
}

I get the following errors with GCC 4.7: 我在GCC 4.7中遇到以下错误:

In file included from include/c++/4.7.0/vector:63:0,
                 from test.cpp:1:
include/c++/4.7.0/bits/stl_construct.h: In instantiation of 'void std::_Construct(_T1*, _Args&& ...) [with _T1 = S, _Args = {const S&}]':
include/c++/4.7.0/bits/stl_uninitialized.h:77:3:   required from 'static _ForwardIterator std::__uninitialized_copy<_TrivialValueTypes>::__uninit_copy(_InputIterator, _InputIterator, _ForwardIterator) [with _InputIterator = const S*, _ForwardIterator = S*, bool _TrivialValueTypes = false]'
include/c++/4.7.0/bits/stl_uninitialized.h:119:41:   required from '_ForwardIterator std::uninitialized_copy(_InputIterator, _InputIterator, _ForwardIterator) [with _InputIterator = const S*, _ForwardIterator = S*]'
include/c++/4.7.0/bits/stl_uninitialized.h:260:63:   required from '_ForwardIterator std::__uninitialized_copy_a(_InputIterator, _InputIterator, _ForwardIterator, std::allocator<_Tp>&) [with _InputIterator = const S*, _ForwardIterator = S*, _Tp = S]'
include/c++/4.7.0/bits/stl_vector.h:1185:4:   required from 'void std::vector<_Tp, _Alloc>::_M_range_initialize(_ForwardIterator, _ForwardIterator, std::forward_iterator_tag) [with _ForwardIterator = const S*, _Tp = S, _Alloc = std::allocator<S>]'
include/c++/4.7.0/bits/stl_vector.h:362:2:   required from 'std::vector<_Tp, _Alloc>::vector(std::initializer_list<_Tp>, const allocator_type&) [with _Tp = S, _Alloc = std::allocator<S>, std::vector<_Tp, _Alloc>::allocator_type = std::allocator<S>]'
test.cpp:11:41:   required from here
include/c++/4.7.0/bits/stl_construct.h:77:7: error: no matching function for call to 'S::S(const S&)'
include/c++/4.7.0/bits/stl_construct.h:77:7: note: candidates are:
test.cpp:6:5: note: S::S(S&&)
test.cpp:6:5: note:   no known conversion for argument 1 from 'const S' to 'S&&'
test.cpp:5:5: note: S::S(int)
test.cpp:5:5: note:   no known conversion for argument 1 from 'const S' to 'int'

Should this be allowed? 这应该允许吗? I see no technical obstacles to it being allowed, but I don't have the Standard handy at the moment... 我认为它没有任何技术障碍,但我现在还没有标准方便......

Maybe this clause from 8.5.4.5 explains it (my emphasis): 也许8.5.4.5中的这一条款解释了它(我的重点):

An object of type std::initializer_list is constructed from an initializer list as if the implementation allocated an array of N elements of type E, where N is the number of elements in the initializer list. 类型为std :: initializer_list的对象是从初始化列表构造的,就好像实现分配了一个N类型为E的元素的数组,其中N是初始化列表中的元素数。 Each element of that array is copy-initialized with the corresponding element of the initializer list , and the std::initializer_list object is constructed to refer to that array. 使用初始化列表的相应元素对该数组的每个元素进行复制初始化 ,并构造std :: initializer_list对象以引用该数组。

So you can only initialize from lists if the objects are copyable. 因此,如果对象是可复制的,则只能从列表初始化。


Update: As Johannes points out, copy-initialization can be realized by both copy and move constructors, so that alone isn't enough to answer the question. 更新:正如约翰内斯指出的那样,复制初始化可以通过复制和移动构造函数来实现,因此仅靠这一点并不足以回答这个问题。 Here is, however, an excerpt of the specification of the initializer_list class as described in 18.9: 但是,这是18.9中描述的initializer_list类规范的摘录:

  template<class _E>
    class initializer_list
    {
    public:
      typedef _E            value_type;
      typedef const _E&     reference;
      typedef const _E&     const_reference;
      typedef size_t        size_type;
      typedef const _E*     iterator;
      typedef const _E*     const_iterator;

Note how there are no non-constant typedefs! 注意没有非常量的typedef!

I just tried making an IL constructor which would traverse the initializer list via std::make_move_iterator , which failed because const T & cannot be converted to T&& . 我刚刚尝试制作一个IL构造函数,它将通过std::make_move_iterator遍历初始化列表,但由于const T &无法转换为T&& ,因此失败了。

So the answer is: You cannot move from the IL, because the standard says so. 所以答案是:你不能离开IL,因为标准是这样说的。

It appears it might be a compiler issue. 它似乎可能是编译器问题。 This works in g++ 4.5.1 ( click for IdeOne online demo) 这适用于g ++ 4.5.1点击IdeOne在线演示)

Conclusion : It was, in the sense that older g++ implementations did not correctly flag an error; 结论 :从某种意义上说,旧的g ++实现没有正确地标记错误; initializer lists do not support moving their elements (elements are implicitely copied in the process). 初始化列表不支持移动它们的元素(元素在过程中被隐含地复制)。 Thank to Kerrek SB for quoting the helpful phrase from the standard. 感谢Kerrek SB 引用标准中的有用短语


Old proceedings (for the sake of understanding the comments:) 旧程序(为了理解评论:)

Edit Found out that at least g++ 4.6.1+ seem to have your complaint about this code. 编辑发现至少g ++ 4.6.1+似乎对此代码抱有抱怨。

Edit Upon reading the source to std::initializer_list<T> I'm starting to get the impression that this is not supported by the library (it looks intentional). 编辑在读取std::initializer_list<T>的源代码后,我开始觉得库不支持这种情况(看起来有意)。 Whether the standard actually allows for an initializer list to forward the xvalue-ness of it's elements... I wouldn't be surprised if they stopped there (perfect forwarding is still not easily supported in C++0x I think, and not all initializer parameters would need to have the same (deductable) type. 标准是否实际上允许初始化列表转发它的元素的xvalue-ness ...如果它们停在那里我不会感到惊讶(C ++ 0x中我仍然不支持完美转发,而不是全部初始化参数需要具有相同(可扣除)类型。

Anyone with more standardese under his belt care to help out? 在腰带以下有更多标准的人可以帮忙吗? http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2640.pdf http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2640.pdf

#include <vector>

struct S
{
    S(int) {};
    S(S&&) {};
};

int main()
{
    std::vector<S> v = {S(1), S(2), S(3)};
    std::vector<S> w = {std::move(S(1)), std::move(S(2)), std::move(S(3))};

    std::vector<S> or_even_just = {1, 2, 3};
}

The initializer_list only provides const references and const iterators. initializer_list仅提供const引用和const迭代器。 There is no way for the vector to move from that. 矢量无法从中移动。

template<class E> 
class initializer_list {
public:
    typedef E value_type;

    typedef const E& reference;
    typedef const E& const_reference;

    typedef size_t size_type;

    typedef const E* iterator;
    typedef const E* const_iterator;

It seems the answer is No per Kerrek SB's answer . 似乎答案是否为Kerrek SB的答案 But you can achieve something similar by small helper functions using variadic templates: 但是您可以使用可变参数模板通过小辅助函数实现类似的功能:

#include <vector>
#include <utility>

template <typename T>
void add_to_vector(std::vector<T>* vec) {}

template <typename T, typename... Args>
void add_to_vector(std::vector<T>* vec, T&& car, Args&&... cdr) {
  vec->push_back(std::forward<T>(car));
  add_to_vector(vec, std::forward<Args>(cdr)...);
}

template <typename T, typename... Args>
std::vector<T> make_vector(Args&&... args) {
  std::vector<T> result;
  add_to_vector(&result, std::forward<Args>(args)...);
  return result;
}

struct S {
  S(int) {}
  S(S&&) {}
};

int main() {
  std::vector<S> v = make_vector<S>(S(1), S(2), S(3));
  return 0;
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM