C++ 初始化列表中的就地向量构造(对于具有构造函数参数的类)

C++ 初始化列表中的就地向量构造(对于具有构造函数参数的类),c++,c++11,initialization,initializer-list,C++,C++11,Initialization,Initializer List,可能重复: 编辑1:请考虑重新开放投票:我的问题强调地方建设。移动构造是另一种选择,但不是这个问题的内容。谢谢你的回答 编辑2:因为我不能回答这个问题(它被关闭了),我在这里发布了我自己的建议。下面的答案不如我接受的答案好,但可能对其他人有用。至少只调用移动构造函数: 通过std::initializer_list构造对象与从任何其他对象构造对象没有什么不同。std::initializer_list不是一个神秘的幻觉结构;它是一个活生生的、呼吸的C++对象(虽然是临时对象)。因此,它遵循规则

可能重复:

编辑1:请考虑重新开放投票:我的问题强调地方建设。移动构造是另一种选择,但不是这个问题的内容。谢谢你的回答

编辑2:因为我不能回答这个问题(它被关闭了),我在这里发布了我自己的建议。下面的答案不如我接受的答案好,但可能对其他人有用。至少只调用移动构造函数:


通过
std::initializer_list
构造对象与从任何其他对象构造对象没有什么不同。
std::initializer_list
不是一个神秘的幻觉结构;它是一个活生生的、呼吸的C++对象(虽然是临时对象)。因此,它遵循规则的生活,呼吸C++对象的所有规则。 聚合初始化可以有效地省略复制/移动,因为它是聚合初始化,一种纯粹的编译时构造<代码>标准::向量是很多东西;聚合和纯编译时构造不在其中。因此,为了使它自己从给定的东西初始化,它必须执行实际的C++代码,而不是编译时的东西。它必须迭代
初始值设定项列表的每个元素,并复制或移动这些值。后者是不可能的,因为
std::initializer\u list
不提供对其成员的非
const
访问


初始值设定项列表初始化看起来像聚合初始化,而不是像它那样执行。这就是在代码段中创建运行时动态抽象(如
std::vector

列表初始化
std::vector
)的成本,这与执行以下操作无异(如果
initializer\u List
具有公共非显式构造函数或
std::vector
接受数组引用):

只是

typedef T const* iterator;
因此,从
std::initializer\u列表中移出也是毫无疑问的

现在,有解决办法吗?是的,有,事实上,这是一个相当简单的问题

我们希望有一个自由函数,它接受一个容器和一个元组,元组的数量等于您要放置的元素的数量。元组容器将参数传递给容器类型的构造函数。理论上容易,实践上容易(代码中的
索引==seq
构建索引==gen_seq
):

#包括
#包括
#包括
使用别名=T的模板;
使用RemoveRef=typename std::remove_reference::type的模板;
模板
无效模板(C&C、序列、元组和ts){
c、 后置(std::get(std::forward(ts))…);
}
使用Size=std::tuple\u Size的模板;
模板
空位置返回(C&C、元组和…ts){
c、 保留(sizeof…(元组));
别名{(
炮位向后(c,gen,std::forward(ts))
, '0')...};
}


上面的代码调用
emplace\u back\u one
精确地
sizeof…(Tuples)
次,按照传递给
emplace\u back的顺序一次传递一个tuple
。此代码也按从左到右的顺序排列,这意味着构造函数的调用顺序与传递元组的顺序相同
emplace\u back\u one
然后简单地用索引技巧解压元组,并将参数传递给
c。emplace\u back

我认为第一个构造函数正在构造初始化列表,然后向量完美地从中移动构造。我不确定,但这是有意义的。它不是重复的,我是在进行就地初始化。移动施工将是第二种选择。没错,这个问题的首要答案有助于解释发生了什么。这并不意味着问题是一样的。@JohanLundberg:如果你不能用初始值设定项列表移动初始化向量,你当然不能用一个初始值设定项列表放置它。我认为这可以更好地解释可能的原理(尽管我发现当前的行为不直观)。你为什么要避免移动构造?emplace解决方案也要求可以访问移动构造函数或复制构造函数(如果前者正在抛出),即使它没有被调用。聚合初始化实际上不是编译时构造<数组a{{{1,2},{3,4},{5,6}}当然需要运行时构造函数调用和动态分配。@ XEO:但仅仅是因为中间有一个非聚集。一旦进入
向量
,它就不再是聚合初始化。初始化器列表机制的全部要点是它是可读的。如果你想让这个列表包含
forward\u as\u tuple
内容,每个元素有一个转发调用,那么你最好有一系列
emplace\u back
语句。至少更短了。@Nicolas,是的,但这会自动写那些emplace_回叫,编译时,为什么不呢?实际上,我可能只是在运行时进行迭代,假设构造比循环和参数传递要昂贵得多。@JohanLundberg:“是的,但这会在编译时自动编写emplace_回调,为什么不呢?”因为它在代码中比一系列的
v.emplace\u back
调用占用更多的物理空间。@Nicol:当然,当前的表单可能有点冗长,但您可以随时修改Boost.Preprocessor,然后编写
emplace\u back(v,(1,2)(3,4)(5,6))
,这实际上应该不太难做到另外,在通用代码中,您可能已经有了这些元组,我认为拥有它们非常好。另外,如果您没有临时文件,或者希望专门将内容移动到元素中,则可以使用
std::tie
,这样就不会那么冗长了。@Xeo:您的链接不再有效。你能重新发布完整的例子吗?
std::vector<A2> k{{2,3},{4,5},{8,9}};
  std::vector<A2> k2;
  k2.reserve(3);
  k2.emplace_back(2,3);
  k2.emplace_back(4,5);
  k2.emplace_back(8,9);
std::vector<A2> k{{2,3},{4,5},std::move(A2{8,9})};
#include <vector>
#include <iostream>

struct A2 {
  int mk;
  int mj;
  A2(int k,int j) : mk(k),mj(j) {
    std::cout << "     constr for "<<this<< ":"<< mk<<std::endl;
  }
  A2(const A2& a2) {
    mk=a2.mk;
    mj=a2.mj;    
    std::cout << "copy constr for "<<this<< ":" << mk<<std::endl;
  }
  A2(A2&& a2) noexcept  {
    mk=std::move(a2.mk);
    mj=std::move(a2.mj);
    std::cout << "move constr for "<<this<< ":"<< mk<<std::endl;
  }
};

struct Ano {
  Ano() {
    std::cout << "     constr for "<<this <<std::endl;
  }
  Ano(const Ano& ano) {
    std::cout << "copy constr for "<<this<<std::endl;
  }
  Ano(Ano&& ano) noexcept  {
    std::cout << "move constr for "<<this<<std::endl;
  }
};


int main (){
  // here both constructor and copy constructor is called:
  std::vector<A2> k{{2,3},{4,5},std::move(A2{8,9})};

  std::cout << "......"<<std::endl;
  std::vector<A2> k2;
  k2.reserve(3);
  // here (naturally) only constructor is called:
  k2.emplace_back(2,3);
  k2.emplace_back(4,5);
  k2.emplace_back(8,9);

  std::cout << "......"<<std::endl;  
  // here only constructor is called:
  std::vector<Ano> anos(3);

}
     constr for 0xbf9fdf18:2
     constr for 0xbf9fdf20:4
     constr for 0xbf9fdf0c:8
move constr for 0xbf9fdf28:8
copy constr for 0x90ed008:2
copy constr for 0x90ed010:4
copy constr for 0x90ed018:8
......
     constr for 0x90ed028:2
     constr for 0x90ed030:4
     constr for 0x90ed038:8
......
     constr for 0x90ed048
     constr for 0x90ed049
     constr for 0x90ed04a
// directly construct with the backing array of 'initializer_list'
std::vector<A2> v(alias<A2[]>{ A2(2,3), A2(4,5), A2(8,9) });
std::initializer_list<T>::iterator
typedef T const* iterator;
#include <type_traits>
#include <tuple>
#include <utility>

template<class T> using alias = T;
template<class T> using RemoveRef = typename std::remove_reference<T>::type;

template<class C, unsigned... Is, class Tuple>
void emplace_back_one(C& c, seq<Is...>, Tuple&& ts){
  c.emplace_back(std::get<Is>(std::forward<Tuple>(ts))...);
}

template<class T> using Size = std::tuple_size<RemoveRef<T>>;

template<class C, class... Tuples>
void emplace_back(C& c, Tuples&&... ts){
  c.reserve(sizeof...(Tuples));
  alias<char[]>{(
    emplace_back_one(c, gen_seq<std::tuple_size<RemoveRef<Tuples>>::value>{}, std::forward<Tuples>(ts))
  , '0')...};
}