【问题标题】:Initializing std::vector with iterative function calls使用迭代函数调用初始化 std::vector
【发布时间】:2012-09-12 18:22:14
【问题描述】:

在许多语言中,都有有助于初始化集合的生成器。在 C++ 中,如果想统一初始化一个向量,可以这样写:

std::vector<int> vec(10, 42); // get 10 elements, each equals 42

如果想即时生成不同的值怎么办?例如,用 10 个随机值,或者从 0 到 9 的连续数字对其进行初始化?这种语法会很方便,但它在 C++11 中不起作用

int cnt = 0;
std::vector<int> vec(10, [&cnt]()->int { return cnt++;});

有没有一种通过迭代函数调用来初始化集合的好方法?我目前使用这种丑陋的模式(不比循环更具可读性/短):

std::vector<int> vec;
int cnt = 0;
std::generate_n(std::back_inserter(vec), 10, [&cnt]()->int { return cnt++;});

有一件事会有所帮助,它可以解释第一个构造函数的缺失。我可以想象一个迭代器,它接受一个函数和调用次数,以便构造函数

vector ( InputIterator first, InputIterator last);

将适用。但是我在标准库中没有找到类似的东西。我错过了吗?第一个构造函数没有达到标准还有其他原因吗?

【问题讨论】:

  • 我不觉得那个模式那么难看,但我喜欢这个问题。我很想知道除了避免类接口膨胀之外是否还有其他原因。
  • 从好的方面来说,C++ 是编写实现所需语义的可迭代对象的完美语言!

标签: c++ c++11 iterator initialization stdvector


【解决方案1】:

遗憾的是,没有标准的工具可以做到这一点。

对于您的具体示例,您可以像这样使用 Boost.Iterator 的 counting_iterator

std::vector<int> v(boost::counting_iterator<int>(0),
    boost::counting_iterator<int>(10));

甚至像这样使用 Boost.Range:

auto v(boost::copy_range<std::vector<int>>(boost::irange(0,10)));

copy_range 基本上只是return std::vector&lt;int&gt;(begin(range), end(range)),并且是对仅支持具有两个迭代器的范围构造的现有容器采用全范围构造的好方法。)


现在,对于具有生成器函数的通用案例(如std::rand),有function_input_iterator。当递增时,它调用生成器并保存结果,然后在取消引用时返回。

#include <vector>
#include <iostream>
#include <cmath>
#include <boost/iterator/function_input_iterator.hpp>

int main(){
  std::vector<int> v(boost::make_function_input_iterator(std::rand, 0),
      boost::make_function_input_iterator(std::rand,10));
  for(auto e : v)
    std::cout << e << " ";
}

Live example.

遗憾的是,由于 function_input_iterator 不使用 Boost.ResultOf,因此您需要一个函数指针或函数对象类型具有嵌套的 result_type。 Lambdas,无论出于何种原因,都没有。您可以将 lambda 传递给 std::function(或 boost::function)对象,该对象定义了这一点。 Here's an example with std::function。只能希望 Boost.Iterator 有一天会使用 Boost.ResultOf,如果定义了BOOST_RESULT_OF_USE_DECLTYPE,它将使用decltype

【讨论】:

  • 嗯。局部结构不能用作 C++03 中的模板参数,而在 C++11 中你有 lambdas。
  • @Konrad:我解释了为什么我在代码下方的 C++11 代码中使用本地结构。基本上,无论出于何种原因,lambda 类型都不会定义 result_type。 :( 在 C++03 中,你总是可以将其定义为非本地结构。
  • 呃。我心照不宣地认为 lambda 定义了这一点,但公平地说,没有理由——毕竟我们有 result_of。 Boost.Iterator 会添加对此的支持吗?
  • @Konrad:只能希望。 :/ 在其他新闻中,更新了代码以针对序列的特定情况显示更好/更简洁的方式。
  • 您的回答是最接近我期望的回答。谢谢!
【解决方案2】:

世界太大,C++ 无法为所有事情提供解决方案。然而,C++ 并不想成为一个巨大的超市,里面装满了满足各种口味的即食食品。相反,它是一个设备齐全的小厨房,,C++ Master Chef 可以在其中烹制任何您想要的解决方案。

这是序列生成器的一个愚蠢且非常粗略的示例:

#include <iterator>

struct sequence_iterator : std::iterator<std::input_iterator_tag, int>
{
    sequence_iterator() : singular(true) { }
    sequence_iterator(int a, int b) : singular(false) start(a), end(b) { }
    bool singular;
    int start;
    int end;

    int operator*() { return start; }
    void operator++() { ++start; }

    bool operator==(sequence_iterator const & rhs) const
    {
        return (start == end) == rhs.singular;
    }
    bool operator!=(sequence_iterator const & rhs) const
    {
        return !operator==(rhs);
    }
};

现在你可以说:

std::vector<int> v(sequence_iterator(1,10), sequence_iterator());

同样,您可以编写一个更通用的小工具,“调用给定的函子给定的次数”等(例如,通过模板复制获取一个函数对象,并将计数器用作重复计数器;以及取消引用调用函子)。

【讨论】:

  • 非默认构造函数上的singular不应该显式初始化为false吗?
  • 为什么不简单地从 std::iterator 继承 typedefs? :)
  • 我同意你的观点,序列生成可能过于具体而无法包含在 C++ 中。但我的问题更笼统:我询问任何迭代函数调用,例如RNG 初始化。其实重点是把vector constructor和generate_n合并起来,更加优雅。
  • @Xeo:因为我不知道怎么做! :-) [编辑:] 完成,谢谢!
  • @overrider:是的,“generate_n”-iterator 将是一件好事...... getline-iterator 也是如此。好吧,也许有一天。但现在,你知道如何自己制作了!
【解决方案3】:

如果您使用的编译器支持您在问题中使用的 lambda,那么很有可能它还包括 std::iota,这至少使计数情况更清晰:

std::vector <int> vec(10);
std::iota(begin(vec), end(vec), 0);

对于这种情况(我认为还有很多其他情况),我们真的更喜欢iota_n

namespace stdx {
template <class FwdIt, class T>
void iota_n(FwdIt b, size_t count, T val = T()) {
    for ( ; count; --count, ++b, ++val)
        *b = val;
}
}

对于您的情况,您可以这样使用:

std::vector<int> vec;

stdx::iota_n(std::back_inserter(vec), 10);

至于为什么它没有包含在标准库中,我真的无法猜测。我想这可以被视为支持范围的论据,因此该算法将采用一个范围,并且我们有一种简单的方法可以从开始/结束对或开始/计数对创建范围。不过,我不确定我是否完全同意这一点——范围在某些情况下似乎确实运作良好,但在其他情况下,它们几乎没有意义。我不确定如果没有更多的工作,我们得到的答案确实比一对迭代器要好得多。

【讨论】:

    【解决方案4】:

    没有人提到boost::assign,所以在这里介绍一下:

    示例

    #include <iostream>
    #include <vector>
    #include <boost/assign/std/vector.hpp> 
    #include <cstdlib>
    
    int main()
    {
        std::vector<int> v1;
        std::vector<int> v2;
        boost::assign::push_back(v1).repeat_fun(9, &rand);
        int cnt = 0;
        boost::assign::push_back(v2).repeat_fun(10, [&cnt]()->int { return cnt++;});
        for (auto i : v1)
        {
            std::cout << i << ' ';
        }
        std::cout << std::endl;
        for (auto i : v2)
        {
            std::cout << i << ' ';
        }
    }
    

    输出

    41 18467 6334 26500 19169 15724 11478 29358 26962
    0 1 2 3 4 5 6 7 8 9

    【讨论】:

    • 不错的功能,但不是初始化。比 std::generate_n 短一点,但需要 boost。
    【解决方案5】:

    您可以使用 SFINAE 来形成表格:

    #include <iostream>
    #include <vector>
    
    template <int n> struct coeff    { static int const value = coeff<n-1>::value + 3; };
    template <>      struct coeff<0> { static int const value = 0; };
    
    template<int... values> struct c1 {static int const value[sizeof...(values)];};
    template<int... values> int const c1<values...>::value[] = {values...};
    
    template<int n, int... values> struct c2 : c2< n-1, coeff<n-1>::value, values...> {};
    template<int... values> struct c2< 0, values... > : c1<values...> {};
    
    template<int n> struct table : c2< n > {
        static std::vector< unsigned int > FormTable()
        {
            return std::vector< unsigned int >( & c2< n >::value[0], & c2< n >::value[n] );
        }
    };
    
    int main()
    {
        const auto myTable = table< 20 >::FormTable();
    
        for ( const auto & it : myTable )
        {
            std::cout<< it << std::endl;
        }
    }
    

    【讨论】:

    • 我不够深刻,无法彻底理解。 :( 当编译时元素的数量未知时,它会起作用吗?
    • @overrider 不,上面是在编译时设置向量的方式
    猜你喜欢
    • 1970-01-01
    • 2013-06-06
    • 1970-01-01
    • 1970-01-01
    • 2017-04-17
    • 2017-04-04
    • 2020-05-14
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多