将向量 < T > 转换为 initializer_list < T >

每个人都从 std::initializer_list创建 std::vector,但是反过来呢?

例如,如果使用 std::initializer_list作为参数:

void someThing(std::initializer_list<int> items)
{
...
}

有些时候,你把你的项目放在 vector<T>而不是文字列表中:

std::vector<int> v;
// populate v with values
someThing(v); // boom! No viable conversion etc.

更一般的问题是: 如何从 STL 迭代器而不仅仅是 std::vector创建 stl::initializer_list

42843 次浏览

显然 不,这不可能。没有这样的构造函数(我相信有很好的理由) ,std::initializer_list是一个奇怪的生物。

您可以做的是更改 someThing()以接受一对迭代器。通过这种方式,只要能够更改该函数的签名(它不在第三方库中,等等) ,就可以得到所需的内容。

答案是否定的,你不能这样做。

std::initializer_list<T>类型的对象是一个轻量级代理对象,它提供对 T 类型对象数组的访问。当下列情况下,std::initializer_list对象就是 自动构造:

  • 在列表初始化中使用括号初始化列表,包括函数调用列表初始化和赋值表达式(不要与构造函数初始化列表混淆)
  • 括号初始化列表绑定到 auto,包括在范围 for 循环中

就库支持而言,std::initializer_list只有一个构造空列表的缺省构造函数,而 它的迭代器是常数。缺少 push_back()成员意味着你不能使用 std::copystd::back_inserter迭代器适配器来填充它,你也不能直接通过这些迭代器进行分配:

#include <algorithm>
#include <initializer_list>
#include <iterator>
#include <vector>


int main()
{
auto v = std::vector<int> { 1, 2 };
std::initializer_list<int> i;
auto it = std::begin(i);
*it = begin(v); // error: read-only variable is not assignable
}

实例

如果你看看标准容器,除了在它们的构造函数/插入器中接受 std::initializer_list之外,它们都有接受迭代器对的构造函数/插入器,并且实现很可能将 initializer_list函数委托给相应的迭代器对函数。例如,在 libc + + 中的 std::vector<T>::insert函数就是一句简单的俏皮话:

 iterator insert(const_iterator __position, initializer_list<value_type> __il)
{return insert(__position, __il.begin(), __il.end());}

您应该按照类似的方式修改代码:

void someThing(std::initializer_list<int> items)
{
someThing(items.begin(), items.end()); // delegate
}


template<class It>
void someThing(It first, It last)
{
for (auto it = first, it != last; ++it) // do your thing
}

当你把项目放在一个向量而不是一个文字列表中时:

std::vector<int> v = { 1, 2 };
auto i = { 1, 2 };
someThing(begin(v), end(v)); // OK
someThing(i); // also OK
someThing({1, 2}); // even better

是的,你可以这样做,但你不想这样做,因为你必须这样做是非常愚蠢的。

首先,确定列表的最大长度是多少。必须有一个最大长度,因为 size_t不是无界的。最好找一个更好(更小)的,比如10个。

其次,编写魔术开关代码,该代码接受一个运行时整数,并将其映射到一个编译时整数,然后使用该编译时整数调用一个模板类或函数。这样的代码需要一个最大整数大小——使用上面的最大长度。

现在,魔术般地将向量的大小转换为编译时间长度。

创建从 0length-1的整数的编译时间序列。以 initializer_list构造解压该序列,每次在 std::vector上调用 []。用得到的 initializer_list调用函数。

上面的内容很棘手,也很荒谬,大多数编译器都会因此崩溃。有一个步骤我不确定其合法性——构建 initializer_list是否是一个合法的地点来进行 varard 论证解包?

下面是一个魔术开关的例子: 我可以将编译时策略的创建和使用位置分开吗?

下面是索引或序列技巧的一个示例: 元组中的构造函数参数

这篇文章应该只是理论上的兴趣,因为实际上这是一个非常愚蠢的方法来解决这个问题。

如果不做 n ^ 2的工作,那么使用任意的迭代就更难了。但是因为上面的内容已经够荒谬的了,而且任意的可迭代版本将会更加荒谬... ... (也许使用一个 lambdas 包——让它能够按顺序计算参数是很棘手的。在对初始值设定项列表的各个参数求值之间是否有一个序列点?)

我发布了一种似乎可行但不幸造成内存访问冲突的方法,因为 initializer _ list 被视为对局部作用域内值副本的引用。

还有一个办法。为每个可能的项目数生成一个单独的函数和一个单独的静态初始值设定项列表,这些项目使用参数包进行计数。这不是线程安全的,并且使用 const _ cast (被认为是非常糟糕的)写入静态初始化器 _ list 内存。但是,它在 gcc 和 clang 中都能正常工作。

如果由于某些模糊的原因,您需要解决这个问题,并且没有其他选择,您可以尝试这种方法。

#include <initializer_list>
#include <iostream>
#include <stdexcept>
#include <type_traits>
#include <vector>


namespace __range_to_initializer_list {


constexpr size_t DEFAULT_MAX_LENGTH = 128;


template <typename V> struct backingValue { static V value; };
template <typename V> V backingValue<V>::value;


template <typename V, typename... Vcount> struct backingList { static std::initializer_list<V> list; };
template <typename V, typename... Vcount>
std::initializer_list<V> backingList<V, Vcount...>::list = {(Vcount)backingValue<V>::value...};


template <size_t maxLength, typename It, typename V = typename It::value_type, typename... Vcount>
static typename std::enable_if< sizeof...(Vcount) >= maxLength,
std::initializer_list<V> >::type generate_n(It begin, It end, It current)
{
throw std::length_error("More than maxLength elements in range.");
}


template <size_t maxLength = DEFAULT_MAX_LENGTH, typename It, typename V = typename It::value_type, typename... Vcount>
static typename std::enable_if< sizeof...(Vcount) < maxLength,
std::initializer_list<V> >::type generate_n(It begin, It end, It current)
{
if (current != end)
return generate_n<maxLength, It, V, V, Vcount...>(begin, end, ++current);


current = begin;
for (auto it = backingList<V,Vcount...>::list.begin();
it != backingList<V,Vcount...>::list.end();
++current, ++it)
*const_cast<V*>(&*it) = *current;


return backingList<V,Vcount...>::list;
}


}


template <typename It>
std::initializer_list<typename It::value_type> range_to_initializer_list(It begin, It end)
{
return __range_to_initializer_list::generate_n(begin, end, begin);
}


int main()
{
std::vector<int> vec = {1,2,3,4,5,6,7,8,9,10};
std::initializer_list<int> list = range_to_initializer_list(vec.begin(), vec.end());
for (int i : list)
std::cout << i << std::endl;
return 0;
}

如果你不介意复印件的话,我想这样的东西会有用的:

template<class Iterator>
using iterator_init_list = std::initializer_list<typename std::iterator_traits<Iterator>::value_type>;


template<class Iterator, class... Ts>
iterator_init_list<Iterator> to_initializer_list(Iterator start, Iterator last, Ts... xs)
{
if (start == last) return iterator_init_list<Iterator>{xs...};
else return to_initializer_list(start+1, last, xs..., *start);
}

我认为最好的解决方案是不模板模糊的迭代器类,为了传递向量,使用它的两个方法返回迭代器,只是在一个带有向量的函数中实现函数逻辑。

void someThing(std::initializer_list<int> items)
{
std::vector<int> v;
for(int i:items)
{
v.push_back(i);
}
someThing(v);
}


void someThing(std::vector<int> items)
{
...
}
std::vector<int> v;
someThing(std::initializer_list<int>(&v.front(), &v.front() + v.size()));