将vector <t>转换为initializer_list <t> </t> </t>

时间:2013-09-19 13:11:08

标签: c++ c++11 stl initializer-list

每个人都从std::vector创建std::initializer_list,但另一种方式呢?

例如。如果您使用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

7 个答案:

答案 0 :(得分:26)

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

std::initializer_list<T>类型的对象是一个轻量级代理对象,它提供对类型为T的对象数组的访问。std::initializer_list对象自动构建时:

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

就库支持而言,std::initializer_list只有一个构造空列表的默认构造函数,而它的迭代器是常量。缺少push_back()成员意味着您无法申请一个带有std::copy迭代器适配器的std::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
}

Live Example

如果你看一下标准容器,除了在它们的构造函数/插入器中接受std::initializer_list之外,它们都有构造函数/插入器带有迭代器对,并且实现可能会委托initializer_list函数到相应的迭代器对函数。例如。 std::vector<T>::insert function in libc++就是这个简单的单行:

 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

答案 1 :(得分:7)

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

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

答案 2 :(得分:5)

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

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

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

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

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

以上是棘手和荒谬的,大多数编译器都会对它嗤之以鼻。有一个步骤,我不确定合法性 - 是initializer_list构建一个合法的地方来进行varardic论证解包吗?

以下是魔术开关的示例:Can I separate creation and usage locations of compile-time strategies?

以下是索引或序列技巧的示例:Constructor arguments from tuple

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

使用任意迭代执行此操作更难,无需执行n ^ 2工作。但是由于上面已经足够荒谬了,而且任意的可迭代版本会更加荒谬......(也许有一堆lambdas - 得到它以便按顺序评估参数可能会很棘手。是否有一个序列点评估初始化列表的各种参数?)

答案 3 :(得分:1)

我发布了一种似乎有效的方法,但遗憾的是导致内存访问违规,因为初始化列表被视为对本地范围的值副本的引用。

这是另一种选择。为每个可能的项目数生成单独的函数和单独的静态初始化列表,这些项目使用参数包计数。这不是线程安全的,并使用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;
}

答案 4 :(得分:1)

对于矢量,您可以使用指针算法。

Foo( std::initializer_list< _Type >( aVector.data(), aVector.data() + aVector.size() ) ) ;

您可以将其包装在通用函数中。

template< typename _Type >
auto    InitList( const _Type * begin, size_t size )
{
  return std::initializer_list< _Type >( begin, begin + size ) ;
}

这样调用函数。

Foo( InitList( aVector.data(), aVector.size() ) ) ;

答案 5 :(得分:0)

如果你不介意副本,那么我认为这样的事情会起作用:

origin

答案 6 :(得分:0)

我认为最好的解决方案是在使用矢量的函数中实现函数逻辑,而无需使用其两种返回迭代器的方法来为传递矢量的好处而对晦涩的迭代器类进行模板化。

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)
{
...
}