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

Convert a vector<T> to initializer_list<T>

本文关键字:gt lt initializer list 转换      更新时间:2023-10-16

每个人都从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 可迭代对象创建stl::initializer_list,而不仅仅是std::vector

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

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

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

就库支持而言,std::initializer_list只有一个构造空列表的默认构造函数,并且其迭代器是常量。缺少push_back()成员意味着您无法应用例如带有std::back_inserter迭代器适配器的std::copy来填充它,也不能直接通过此类迭代器进行分配:

#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

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

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

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

首先,确定列表的最大长度。 必须有一个最大长度,因为size_t不是无限的。 理想情况下,找到一个更好(更小)的,比如 10。

其次,编写采用运行时整数并将其映射到编译时整数的

幻开关代码,然后使用该编译时整数调用模板类或函数。 这样的代码需要一个最大整数大小 - 使用上面的最大长度。

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

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

以上是棘手和荒谬的,大多数编译器都会爆炸。 有一个步骤我不确定的合法性 - 建造一个initializer_list是做瓦拉迪克论证的合法场所吗?

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

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

这篇文章应该只具有理论意义,因为实际上这是解决这个问题的一种非常愚蠢的方法。

使用任意可迭代对象执行此操作更难,而不做 n^2 工作。 但是由于上面已经够荒谬了,任意可迭代的版本会更荒谬...... (也许有一包lambda——让它按顺序评估参数可能会很棘手。 在对初始值设定项列表的各种参数的评估之间是否存在一个序列点?

我发布了一种似乎有效的方法,但不幸的是,由于initializer_lists被视为对本地范围的值副本的引用,因此导致内存访问冲突。

这是一个替代方案。 为每个可能数量的项生成一个单独的函数和一个单独的静态初始值设定项列表,这些项使用参数包进行计数。 这不是线程安全的,并使用const_cast(被认为是非常糟糕的)写入静态initializer_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()));