为什么C ++标准库中没有transform_if?
一个用例想要做一个复制(1.可以copy_if),但是从一个容器的值到一个指向这些值的指针容器(2.可以transform
)。
使用可用的工具,我不能在两个步骤内完成:
#include <vector> #include <algorithm> using namespace std; struct ha { int i; explicit ha(int a) : i(a) {} }; int main() { vector<ha> v{ ha{1}, ha{7}, ha{1} }; // initial vector // GOAL : make a vector of pointers to elements with i < 2 vector<ha*> ph; // target vector vector<ha*> pv; // temporary vector // 1. transform(v.begin(), v.end(), back_inserter(pv), [](ha &arg) { return &arg; }); // 2. copy_if(pv.begin(), pv.end(), back_inserter(ph), [](ha *parg) { return parg->i < 2; }); // 2. return 0; }
当然我们可以在pv
上调用remove_if
,并且不需要临时的,但是更好的是,对于一元运算来说,并不难(例如:
template < class InputIterator, class OutputIterator, class UnaryOperator, class Pred > OutputIterator transform_if(InputIterator first1, InputIterator last1, OutputIterator result, UnaryOperator op, Pred pred) { while (first1 != last1) { if (pred(*first1)) { *result = op(*first1); ++result; } ++first1; } return result; } // example call transform_if(v.begin(), v.end(), back_inserter(ph), [](ha &arg) { return &arg; }, // 1. [](ha &arg) { return arg.i < 2; });// 2.
- 是否有可用的C ++标准库工具更优雅的解决方法?
- 是否有一个原因,为什么
transform_if
不存在于图书馆? 现有工具的组合是否具有足够的解决方法和/或认为性能performance良好?
标准库支持基本algorithm。
如果可能的话,容器和algorithm应该是相互独立的。
同样地,可以由现有algorithm组成的algorithm也很less被包括在内,作为简写。
如果你需要一个转换,你可以简单地写下来。 如果你想要它/今天/,现成的组成,而不是开销,你可以使用一个范围库,有一个惰性范围 ,如Boost.Range ,例如:
v | filtered(arg1 % 2) | transformed(arg1 * arg1 / 7.0)
正如@hvd在注释中指出的那样, transform_if
double将导致不同的types(在本例中为double
)。 排列顺序很重要,用Boost Range也可以这样写:
v | transformed(arg1 * arg1 / 7.0) | filtered(arg1 < 2.0)
导致不同的语义。 这带动了家的重点:
将
std::filter_and_transform
,std::transform_and_filter
,std::filter_transform_and_filter
等等包含到标准库中是没有意义的 。
看Live Live Coliru的样本
#include <boost/range/algorithm.hpp> #include <boost/range/adaptors.hpp> using namespace boost::adaptors; // only for succinct predicates without lambdas #include <boost/phoenix.hpp> using namespace boost::phoenix::arg_names; // for demo #include <iostream> int main() { std::vector<int> const v { 1,2,3,4,5 }; boost::copy( v | filtered(arg1 % 2) | transformed(arg1 * arg1 / 7.0), std::ostream_iterator<double>(std::cout, "\n")); }
在许多方面新的for循环符号减less了对访问集合中每个元素的algorithm的需求,在这个集合中,现在只需编写一个循环并把逻辑放在原来的位置就可以干净了。
std::vector< decltype( op( begin(coll) ) > output; for( auto const& elem : coll ) { if( pred( elem ) ) { output.push_back( op( elem ) ); } }
现在是否真的提供了很多价值来joinalgorithm? 虽然是的,这个algorithm对于C ++ 03来说是非常有用的,事实上我已经有了一个,现在我们不需要它,所以在添加它的时候没有什么好处。
请注意,在实际使用中,您的代码不会总是看起来如此:您不一定具有“op”和“pred”函数,并且可能必须创buildlambdaexpression式才能使它们适合algorithm。 如果逻辑很复杂,如果只是从inputtypes中提取一个成员并检查其值或将其添加到集合中,那么分离问题是很好的,但比使用algorithm要简单得多。
另外,一旦添加了某种transform_if,则必须决定是在变换之前还是之后应用谓词,或者甚至有两个谓词并将其应用于两个位置。
那么我们该怎么做? 添加3个algorithm? (如果编译器可以在转换的两端应用谓词,用户可以很容易地错误地select错误的algorithm,代码仍然可以编译,但是会产生错误的结果。
另外,如果集合很大,用户是否希望循环迭代器或映射/减less? 随着map / reduce的引入,你会在这个方程中变得更加复杂。
从本质上讲,图书馆提供了工具,用户留在这里使用它们来适应他们想要做的事情,而不是像algorithm经常出现的那样。 (看看上面的用户如何使用累积来扭曲事情,以适应他们真正想做的事情)。
举一个简单的例子,一张地图。 对于每个元素,如果密钥均匀,我将输出该值。
std::vector< std::string > valuesOfEvenKeys ( std::map< int, std::string > const& keyValues ) { std::vector< std::string > res; for( auto const& elem: keyValues ) { if( elem.first % 2 == 0 ) { res.push_back( elem.second ); } } return res; }
好而简单。 花式适合transform_ifalgorithm?
该标准的devise是为了尽量减less重复。
在这个特殊的情况下,你可以通过一个简单的range-for循环以更简洁明了的方式达到algorithm的目标。
// another way vector<ha*> newVec; for(auto& item : v) { if (item.i < 2) { newVec.push_back(&item); } }
我已经修改了这个例子,以便它编译,添加了一些诊断,并提出了OP的algorithm和我的肩并肩。
#include <vector> #include <algorithm> #include <iostream> #include <iterator> using namespace std; struct ha { explicit ha(int a) : i(a) {} int i; // added this to solve compile error }; // added diagnostic helpers ostream& operator<<(ostream& os, const ha& t) { os << "{ " << ti << " }"; return os; } ostream& operator<<(ostream& os, const ha* t) { os << "&" << *t; return os; } int main() { vector<ha> v{ ha{1}, ha{7}, ha{1} }; // initial vector // GOAL : make a vector of pointers to elements with i < 2 vector<ha*> ph; // target vector vector<ha*> pv; // temporary vector // 1. transform(v.begin(), v.end(), back_inserter(pv), [](ha &arg) { return &arg; }); // 2. copy_if(pv.begin(), pv.end(), back_inserter(ph), [](ha *parg) { return parg->i < 2; }); // 2. // output diagnostics copy(begin(v), end(v), ostream_iterator<ha>(cout)); cout << endl; copy(begin(ph), end(ph), ostream_iterator<ha*>(cout)); cout << endl; // another way vector<ha*> newVec; for(auto& item : v) { if (item.i < 2) { newVec.push_back(&item); } } // diagnostics copy(begin(newVec), end(newVec), ostream_iterator<ha*>(cout)); cout << endl; return 0; }
很抱歉在这么长时间后重新提出这个问题。 我最近有类似的要求。 我通过编写一个采用boost :: optional的back_insert_iterator来解决这个问题:
template<class Container> struct optional_back_insert_iterator : public std::iterator< std::output_iterator_tag, void, void, void, void > { explicit optional_back_insert_iterator( Container& c ) : container(std::addressof(c)) {} using value_type = typename Container::value_type; optional_back_insert_iterator<Container>& operator=( const boost::optional<value_type> opt ) { if (opt) { container->push_back(std::move(opt.value())); } return *this; } optional_back_insert_iterator<Container>& operator*() { return *this; } optional_back_insert_iterator<Container>& operator++() { return *this; } optional_back_insert_iterator<Container>& operator++(int) { return *this; } protected: Container* container; }; template<class Container> optional_back_insert_iterator<Container> optional_back_inserter(Container& container) { return optional_back_insert_iterator<Container>(container); }
像这样使用:
transform(begin(s), end(s), optional_back_inserter(d), [](const auto& s) -> boost::optional<size_t> { if (s.length() > 1) return { s.length() * 2 }; else return { boost::none }; });
template <class InputIt, class OutputIt, class BinaryOp> OutputIt transform_if(InputIt it, InputIt end, OutputIt oit, BinaryOp op) { for(; it != end; ++it, (void) ++oit) op(oit, *it); return oit; }
用法:(请注意,CONDITION和TRANSFORM不是macros,它们是占位符,用于任何需要应用的条件和转换)
std::vector a{1, 2, 3, 4}; std::vector b; return transform_if(a.begin(), a.end(), b.begin(), [](auto oit, auto item) // Note the use of 'auto' to make life easier { if(CONDITION(item)) // Here's the 'if' part *oit++ = TRANSFORM(item); // Here's the 'transform' part } );
为什么我们不能写自己的transform_if? 例如:
template <class Iterator, class Predicate, class Function> void transform_if(Iterator first, Iterator last, Predicate pred, Function func){ while (first != last) { if (pred(first)) func(*first); first++; } }