Raw循环与依赖于索引的循环算法

时间:2018-04-05 17:31:01

标签: c++ c++11

我正在研究性能重要的大代码。我读到的一件事是应该避免使用原始循环,并替换为for_each,基于范围的for循环或STL算法等等。问题在于,在所有(大多数)示例中,一切看起来都适合于问题,即for_each显示cout例程* eye roll *。

在我的情况下,循环内的索引很重要(除非你另外告诉我)。例如,我想创建这样的表:

std::vector<double> vect1 (nmax), vect2 (nmax);
for (size_t i{0}; i < nmax; ++i) {
    vect1[i] = f(i);  // f is a routine defined somewhere else
    vect2[i] = f(i)+2.0;
}

我可以使用的是带有lambda函数的generate函数,它将是这样的:

std::vector<double> vect1 (nmax), vect2 (nmax);
size_t count{0};
generate(vect1.begin(), vect1.end(), [&]() {return f(count++);});

count=0;
generate(vect2.begin(), vect2.end(), [&]() {return f(count++) + 2.0;});

但我并不喜欢这个,因为:

  1. 计数一直存在,直到例程结束。

  2. 我们看到使用另一个函数,我必须将count重置为零并再次生成另一个向量。我必须跟踪所有计数变量等。使用for循环,我可以把它放在同一个循环中。

  3. 使用for循环,很容易看到对应关系。我在左边和右边。有了generate,我觉得它在左右两侧都有一个不同的变量,这意味着潜在的错误。

  4. 我只能计算++,而不是++ count,这意味着变量的复制。

  5. 当然,这是一个简单的例子。但我想知道generate()版本对于这类事情是否更好(代码/性能/可读性)。或者也许有更好的方法,我对所有建议和评论持开放态度。

    谢谢!

4 个答案:

答案 0 :(得分:2)

我写了index范围让我:

std::vector<double> vect1 (nmax), vect2 (nmax);
for (auto i : index_upto(nmax))
  vect1[i] = f(i);  // f is a routine defined somewhere else
  vect2[i] = f(i)+2.0;
}

取消了手动fenceposting,但保留了代码不变。

这并不是那么难。编写一个存储T的伪迭代器,并在一元*上返回一个副本。它应该支持==++(将两者都传递到存储的T)。

template<class T>
struct index_it {
  T t;
  index_it& operator++() { ++t; return *this; }
  index_it operator++(int) { auto r = *this; ++*this; return r; }
  friend bool operator==( index_it const& lhs, index_it const& rhs ) {
    return lhs.t == rhs.t;
  }
  friend bool operator!=( index_it const& lhs, index_it const& rhs ) {
    return lhs.t != rhs.t;
  }
  T operator*()const& { return t; }
  T operator*()&& { return std::move(t); }
};

接下来,写一个范围:

template<class It>
struct range {
  It b, e;
  It begin() const { return b; }
  It end() const { return e; }
};

然后组成两个。

template<class T>
using index_range = range<index_it<T>>;

template<class T>
index_range<T> make_index_range( T s, T f ) {
  return {{std::move(s)}, {std::move(f)}};
}

index_range<std::size_t> index_upto( std::size_t n ) {
  return make_index_range( std::size_t(0), n );
}

请注意,index_it 不是迭代器,但非常像一个迭代器。您可以完成它并使其成为输入迭代器;除此之外,你会遇到问题,因为迭代器期望支持容器。

答案 1 :(得分:1)

我们可以使用一个可变的lambda ...

#include <vector>
#include <algorithm>

double f(int x) { return x*2; }

int main()
{
    constexpr int nmax = 100;
    std::vector<double> vect1 (nmax), vect2 (nmax);

    std::generate(vect1.begin(), 
                  vect1.end(), 
                  [count = int(0)]() mutable { return f(count++); });

    std::generate(vect2.begin(), 
                  vect2.end(), 
                  [count = int(0)]() mutable { return f(count++) + 2.0; });

}

另一个选项(使用c ++ 17进行模板参数推导):

template<class F>
struct counted_function
{
    constexpr counted_function(F f, int start = 0, int step = 1) 
    : f(f)
    , counter(start)
    , step(step) {}

    decltype(auto) operator()() {
        return f(counter++);
    }

    F f;
    int counter;
    int step;
};

用作:

std::generate(vect2.begin(), 
              vect2.end(), 
              counted_function([](auto x) { return f(x) + 2.0; }));

最后,为了好玩,可以写下这个:

generate(vect2).invoking(f).with(every<int>::from(0).to(nmax - 1));

...如果我们写了这样的东西......

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

double f(int x) { return x*2; }

template<class T> struct value_iter
{
    using value_type = T;
    using difference_type = T;
    using reference = T&;
    using pointer = T*;
    using iterator_category = std::forward_iterator_tag;

    friend bool operator==(value_iter l, value_iter r)
    {
        return l.current == r.current;
    }

    friend bool operator!=(value_iter l, value_iter r)
    {
        return !(l == r);
    }

    T const& operator*() const& { return current; }
    value_iter& operator++() { ++current; return *this; }

    T current;
};

template<class T> struct every
{
    struct from_thing
    {
        T from;

        struct to_thing
        {
            auto begin() const { return value_iter<T> { from };}
            auto end() const { return value_iter<T> { to+1 };}
            T from, to;
        };

        auto to(T x) { return to_thing { from, x }; }
    };

    static constexpr auto from(T start)
    {
        return from_thing { start };
    }

};

template<class F>
struct counted_function
{
    constexpr counted_function(F f, int start = 0, int step = 1) 
    : f(f)
    , counter(start)
    , step(step) {}

    decltype(auto) operator()() {
        return f(counter++);
    }

    F f;
    int counter;
    int step;
};

template <class Container> struct generate
{
    generate(Container& c) : c(c) {}

    template<class F>
    struct invoking_thing
    {

        template<class Thing>
        auto with(Thing thing)
        {
            using std::begin;
            using std::end;
            std::copy(begin(thing), end(thing), begin(c));
            return c;
        }

        F f;
        Container& c;
    };

    template<class F>
    auto invoking(F f) { return invoking_thing<F>{f, c}; }

    Container& c;
};

int main()
{
    constexpr int nmax = 100;
    std::vector<double> vect2 (nmax);

    generate(vect2).invoking(f).with(every<int>::from(0).to(nmax - 1));
}

答案 2 :(得分:1)

使用有状态的lambda并不是一个好主意。您可能最好编写自己的generate函数,该函数使函数对象接收迭代器:

template<class ForwardIt, class Generator>
void generate_iter(ForwardIt first, ForwardIt last, Generator g) {
    while (first != last) {
        *first = g(first);
        ++first;
    }
}

您可以按如下方式使用它:

generate_iter(vect1.begin(), vect1.end(), [&](const std::vector<double>::iterator& iter) {
    auto count = std::distance(vect1.begin(), iter);
    return f(count);
});

Demo.

答案 3 :(得分:0)

使用range-v3,它将类似于:

auto vect1 = ranges::view::ints(0, nmax) | ranges::view::transform(f);
auto vect2 = ranges::view::ints(0, nmax) | ranges::view::transform(f2);
// or auto vect2 = vect1 | ranges::view::transform([](double d){ return d + 2.; });

Demo