Nik*_*iou 72 c++ c++-standard-library stl-algorithm
当想要执行一个副本(1. doable with 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;
}
Run Code Online (Sandbox Code Playgroud)
Ofcourse我们可以调用remove_if上pv并消除需要一个临时的,更好的是虽然,它并不难实现(对于一元运算)是这样的:
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.
Run Code Online (Sandbox Code Playgroud)
transform_if库中没有原因吗?现有工具的组合是否具有足够的解决方案和/或被认为是性能良好的表现?seh*_*ehe 33
标准库有利于基本算法.
如果可能,容器和算法应该彼此独立.
同样,可以由现有算法组成的算法很少被包括在内,作为简写.
如果你需要转换if,你可以轻而易举地写它.如果你想要它/ today /,组成现成品并且不会产生开销,你可以使用具有惰性范围的范围库,例如Boost.Range,例如:
v | filtered(arg1 % 2) | transformed(arg1 * arg1 / 7.0)
Run Code Online (Sandbox Code Playgroud)
正如@hvd在注释中指出的那样,transform_ifdouble会产生不同的类型(double在本例中).组成顺序很重要,使用Boost Range你也可以写:
v | transformed(arg1 * arg1 / 7.0) | filtered(arg1 < 2.0)
Run Code Online (Sandbox Code Playgroud)
导致不同的语义.这促成了这一点:
这让很少的意义,包括
std::filter_and_transform,std::transform_and_filter,std::filter_transform_and_filter等等进入标准库.
查看Live On 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"));
}
Run Code Online (Sandbox Code Playgroud)
很抱歉在这么长时间后重新提出这个问题。我最近也有类似的需求。我通过编写一个采用 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);
}
Run Code Online (Sandbox Code Playgroud)
像这样使用:
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 };
});
Run Code Online (Sandbox Code Playgroud)
新的for循环符号在很多方面减少了对访问集合中每个元素的算法的需求,现在它更简洁,只需编写循环并将逻辑放在原位.
std::vector< decltype( op( begin(coll) ) > output;
for( auto const& elem : coll )
{
if( pred( elem ) )
{
output.push_back( op( elem ) );
}
}
Run Code Online (Sandbox Code Playgroud)
现在放入算法真的提供了很多价值吗?虽然是,但算法对C++ 03有用,实际上我有一个用于它,我们现在不需要一个,所以添加它没有真正的优势.
请注意,在实际使用中,您的代码看起来并不总是如此:您不一定具有"op"和"pred"功能,并且可能必须创建lambdas以使它们"适合"算法.虽然如果逻辑是复杂的,分离问题是很好的,如果只是从输入类型中提取成员并检查其值或将其添加到集合中,那么再次使用算法要简单得多.
此外,一旦添加某种transform_if,就必须决定是在变换之前还是之后应用谓词,或者甚至有两个谓词并在两个地方都应用它.
那我们该怎么办?添加3个算法?(并且在编译器可以在转换的任一端应用谓词的情况下,用户可能容易错误地选择错误的算法并且代码仍然编译但产生错误的结果).
此外,如果集合很大,用户是否想要使用迭代器或map/reduce循环?随着map/reduce的引入,你会在等式中获得更复杂的东西.
从本质上讲,库提供了工具,用户留在这里使用它们来适应他们想要做的事情,而不是通常的算法情况.(看看上面的用户如何尝试使用累积来扭曲事物以适应他们真正想要做的事情).
举一个简单的例子,一张地图.对于每个元素,如果键是偶数,我将输出值.
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;
}
Run Code Online (Sandbox Code Playgroud)
很好,很简单.是否适合transform_if算法?
一段时间后再次找到这个问题,并设计了一整套可能有用的通用迭代器适配器后,我意识到原来的问题只需要std::reference_wrapper.
使用它而不是指针,你很好:
#include <algorithm>
#include <functional> // std::reference_wrapper
#include <iostream>
#include <vector>
struct ha {
int i;
};
int main() {
std::vector<ha> v { {1}, {7}, {1}, };
std::vector<std::reference_wrapper<ha const> > ph; // target vector
copy_if(v.begin(), v.end(), back_inserter(ph), [](const ha &parg) { return parg.i < 2; });
for (ha const& el : ph)
std::cout << el.i << " ";
}
Run Code Online (Sandbox Code Playgroud)
印刷
1 1
Run Code Online (Sandbox Code Playgroud)
C++20 带来了范围以及一组新的算法来对其进行操作。此添加中最强大的工具之一是视图:
借助这些新工具,转换 if 操作可以:
v“使用函数变换向量AB变得简单如下:
v | std::views::filter(B) | std::views::transform(A)
Run Code Online (Sandbox Code Playgroud)
现在可以公平地说,有一种非常直接的方法可以使用标准库进行“转换 if”。
最初的要求可以写成:
struct ha {
int i;
explicit ha(int a) : i(a) {}
};
int main()
{
std::vector<ha> v{ ha{1}, ha{7}, ha{1}, ha{4}, ha{3}, ha{0} };
auto less4 = [](ha const& h) { return h.i < 4; };
auto pnter = [](ha const& h) { return std::addressof(h); };
for (auto vp : v | std::views::filter(less4)
| std::views::transform(pnter))
{
std::cout << vp->i << ' ';
}
}
Run Code Online (Sandbox Code Playgroud)