我想要的是这种行为:void change_if( ForwardIterator first, ForwardIterator last, UnaryPredicate test, UnaryOperation op )
使用for循环实现这一目标的最佳方法是什么?还是有一些我还不知道的STL魔法?
答案 0 :(得分:7)
这可以在不使用boost的情况下完成,但应用标准算法std::for_each
我不建议使用boost来完成这么简单的任务。在项目中包含boost来执行这样一个简单的任务只是一个愚蠢的事。您可以将boost用于此类任务,前提是它已包含在您的项目中。
std::for_each( first, last, []( const T &x ) { if ( test( x ) ) op( x ); } );
或者,如果要更改序列的元素
,则可以删除限定符conststd::for_each( first, last, []( T &x ) { if ( test( x ) ) op( x ); } );
有时当使用序列的整个范围时,使用基于for语句的范围而不是算法更简单,因为使用具有lambda表达式的算法有时会使代码更不可读
for ( auto &x : sequence )
{
if ( test( x ) ) op( x );
}
或者
for ( auto &x : sequence )
{
if ( test( x ) ) x = op( x );
}
答案 1 :(得分:4)
Vlad from Moscow的解决方案是推荐的方法,因为它很简单。
std::transform
标准算法与lambda的“看似虔诚”的使用:
std::transform(first, last, first, [](auto elem) {
return test(elem) ? op(elem) : elem;
});
实际上导致性能下降,因为所有元素将被分配给,而不仅仅是那些满足谓词的元素。要仅修改谓词元素,还需要像kiwi在答案中提到的boost::filter_iterator
之类的内容。
请注意,我在lambda中使用了带有auto
的C ++ 14语法。对于C ++ 11,您需要decltype(*first)
或iterator_traits<ForwardIterator>::value_type
之类的内容。在C ++ 98/03中,你可以使用它和手工制作的函数对象。
答案 2 :(得分:3)
另一个提升解决方案:
http://www.boost.org/doc/libs/1_55_0/libs/iterator/doc/filter_iterator.html
只需在过滤后的迭代器上调用std :: transform。
答案 3 :(得分:2)
std::for_each( first, last, []( T &x ) { if ( test( x ) ) op( x ); } );
或使用boost lambda:
#include <boost/lambda/lambda.hpp>
#include <boost/lambda/bind.hpp>
#include <boost/lambda/if.hpp>
std::for_each( v.begin(), v.end(),
if_( test() )[ op() ]
);
或者:
std::vector<int>::iterator it = v.begin();
while ( it != v.end()) {
if ( test( *it)) op(*it);
++it;
}
答案 4 :(得分:1)
您希望change_if
是一个简单的循环吗?
template<typename ForwardIterator, typename UnaryPredicate>
void change_if( ForwardIterator first, ForwardIterator last, UnaryPredicate test, UnaryOperation op ) {
for(; first!=last; ++first)
if (test(*first)) *first=op(std::move(*first));
}
或者只是写上面的循环。我建议实际写change_if
并调用它,因为虽然上面的代码很短,但我会发现change_if
调用比仅仅删除上面的代码更清楚,而不是更清楚。
我也喜欢编写基于容器的重载:
template<typename Container, typename UnaryPredicate>
void change_if( Container&& c, UnaryPredicate test, UnaryOperation op ) {
for(auto& v : std::forward<Container>(c))
if (test(v)) v=op(std::move(v));
}
但我也有:
template<typename Iterator>
struct range {
Iterator b, e;
Iterator begin() const { return b; }
Iterator end() const { return e; }
};
template<typename Iterator0, typename Iterator1>
range<typename std::decay<Iterator0>::type> make_range(Iterator0&& b, Iterator1&& e) {
static_assert(
std::is_convertible< Iterator1, typename std::decay<Iterator0>::type >::value,
"end must be compatible with begin iterator type"
);
return { std::forward<Iterator0>(b), std::forward<Iterator1>(e) };
}
允许我将这种基于容器的算法与迭代器一起使用。
您会看到我有Container
change_if
?它实际上是基于范围的change_if
。
它被称为:
change_if( myVect, [](int x){return (x%2)==0;}, [](int x){return x/2;} );
在容器上,而不是一对迭代器。但是,如果您只想更改容器的前半部分,则它不起作用:因此乍一看,基于容器(基于范围的)算法的用处不大。
但make_range
将迭代器转换为范围。所以你可以:
change_if( make_range( myVec.begin(), myVec.begin()+myVec.size()/2 ), [](int x){return (x%2)==0;}, [](int x){return x/2;} )
make_range
通过将两个迭代器捆绑到一个range<>
对象中,无法直接将2个迭代器传递给基于范围的算法。这个角落的情况更加冗长,但典型的情况(处理整个容器)变得不那么冗长。
另外,常见的一种错误(为begin
和end
命名一个不同的容器)的频率要低得多。
所有这些最终都比基于迭代器的版本高效或更高效。而且,如果您使用iterables(具有不同begin
和end
迭代器类型的范围)替换您的范围,我的change_if
只能正常工作