23

可以将push_back不可复制但可移动类型的右值转换为该类型的向量:

#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));
}

但是,当我尝试使用相同的右值初始化向量列表时,我收到有关需要复制构造函数的错误:

#include <vector>

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

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

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'

这应该被允许吗?我认为它没有被允许的技术障碍,但我目前手边没有标准......

4

4 回答 4

14

也许 8.5.4.5 中的这个条款解释了它(我的重点):

std::initializer_list 类型的对象是从初始化列表构造的,就好像实现分配了一个 E 类型的 N 个元素的数组,其中 N 是初始化列表中的元素数。该数组的每个元素都使用初始化器列表的相应元素进行复制初始化,并且构造 std::initializer_list 对象以引用该数组。

因此,如果对象是可复制的,您只能从列表中初始化。


更新:正如 Johannes 所指出的,复制初始化可以通过复制和移动构造函数来实现,因此仅凭这一点还不足以回答这个问题。然而,这里是initializer_list18.9 中描述的类规范的摘录:

  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;

注意没有非常量类型定义!

我只是尝试制作一个 IL 构造函数,该构造函数将通过 遍历初始化程序列表std::make_move_iterator,但由于const T &无法转换为T&&.

所以答案是:你不能离开 IL,因为标准是这样说的。

于 2011-08-29T15:18:47.137 回答
9

看来这可能是编译器问题。这适用于 g++ 4.5.1点击查看 IdeOne 在线演示)

结论:从某种意义上说,旧的 g++ 实现没有正确标记错误;初始化器列表不支持移动它们的元素(元素在过程中被隐式复制)。感谢 Kerrek SB 从标准中引用有用的短语


旧程序(为了理解评论:)

编辑发现至少 g++ 4.6.1+ 似乎对这段代码有抱怨。

编辑在阅读源代码后,std::initializer_list<T>我开始觉得图书馆不支持它(它看起来是故意的)。标准是否真的允许初始化列表转发它的元素的xvalue-ness ...如果他们停在那里我不会感到惊讶(我认为 C++0x 中仍然不容易支持完美的转发,而不是全部初始化器参数需要具有相同的(可扣除的)类型。

任何有更多标准的人愿意帮忙吗?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};
}

于 2011-08-29T14:21:09.177 回答
8

initializer_list 仅提供 const 引用和 const 迭代器。向量无法从那里移动。

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;
于 2011-08-29T15:12:56.417 回答
5

根据Kerrek SB 的回答,答案似乎是“否” 。但是您可以通过使用可变参数模板的小型辅助函数来实现类似的功能:

#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;
}
于 2017-03-26T04:19:58.323 回答