我正在研究性能重要的大代码。我读到的一件事是应该避免使用原始循环,并替换为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;});
但我并不喜欢这个,因为:
计数一直存在,直到例程结束。
我们看到使用另一个函数,我必须将count重置为零并再次生成另一个向量。我必须跟踪所有计数变量等。使用for循环,我可以把它放在同一个循环中。
使用for循环,很容易看到对应关系。我在左边和右边。有了generate,我觉得它在左右两侧都有一个不同的变量,这意味着潜在的错误。
我只能计算++,而不是++ count,这意味着变量的复制。
当然,这是一个简单的例子。但我想知道generate()版本对于这类事情是否更好(代码/性能/可读性)。或者也许有更好的方法,我对所有建议和评论持开放态度。
谢谢!
答案 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);
});
答案 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.; });