为什么C ++标准库中没有transform_if?

时间:2014-05-10 10:20:35

标签: c++ c++-standard-library stl-algorithm

当想要执行一个转义副本(1.可以使用copy_if)但是从一个容器值到一个指向这些值的指针容器(2.可以使用transform时)时出现一个用例。

使用可用的工具,我不能在do it内完成不到两步:

#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;
}

当然,我们可以在remove_if上调用pv并消除对临时的需求,但更好的是implement(对于一元操作)并不困难:

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.
  1. 使用可用的C ++标准库工具是否有更优雅的解决方法?
  2. 库中不存在transform_if的原因吗?现有工具的组合是否具有足够的解决方法和/或在性能方面表现良好?

9 个答案:

答案 0 :(得分:30)

标准库支持基本算法。

如果可能,容器和算法应该彼此独立。

同样,可以由现有算法组成的算法很少被包括在内,作为简写。

如果您需要转换,则可以轻松地编写它。如果你想要它/ today /,由现成品组成而不会产生开销,你可以使用具有延迟范围的范围库,例如Boost.Range,例如:

v | filtered(arg1 % 2) | transformed(arg1 * arg1 / 7.0)

正如@hvd在评论中指出的那样,transform_if double导致其他类型(double,在这种情况下)。组成顺序很重要,使用Boost Range你也可以写:

 v | transformed(arg1 * arg1 / 7.0) | filtered(arg1 < 2.0)

导致不同的语义。这就是重点:

  将std::filter_and_transformstd::transform_and_filterstd::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"));
}

答案 1 :(得分:6)

新的for循环符号在很多方面减少了对访问集合中每个元素的算法的需求,现在它更简洁,只需编写循环并将逻辑放在原位。

std::vector< decltype( op( begin(coll) ) > output;
for( auto const& elem : coll )
{
   if( pred( elem ) )
   {
        output.push_back( op( elem ) );
   }
}

现在输入算法真的提供了很多价值吗?虽然是的,但这个算法对C ++ 03很有用,事实上我有一个它,我们现在不需要一个,所以添加它没有真正的优势。

请注意,在实际使用中,您的代码看起来总是与以下内容完全相同:您不一定有功能&#34;操作&#34;和&#34; pred&#34;并且可能必须创建lambdas以使它们适应&#34;算法。虽然如果逻辑是复杂的,如果只是从输入类型中提取成员并检查其值或将其添加到集合中来分离问题是很好的,那么它再次比使用它简单得多算法。

此外,一旦添加某种transform_if,就必须决定是在变换之前还是之后应用谓词,或者甚至有2个谓词并在两个地方都应用它。

那我们该怎么办?添加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;
}         

美好而简单。是否适合将其转换为transform_if算法?

答案 2 :(得分:4)

该标准的设计应尽量减少重复。

在这种特殊情况下,您可以通过简单的范围循环以更加可读和简洁的方式实现算法的目标。

// another way

vector<ha*> newVec;
for(auto& item : v) {
    if (item.i < 2) {
        newVec.push_back(&item);
    }
}

我修改了这个示例,以便编译,添加一些诊断并同时提供OP算法和我的算法。

#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 << "{ " << t.i << " }";
    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;
}

答案 3 :(得分:3)

很抱歉在这么久之后复活了这个问题。我最近有类似的要求。我通过编写一个带有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 };
          });

答案 4 :(得分:2)

在经过一段时间后再次发现此问题之后,devising a whole slew of potentially useful generic iterator adaptors我意识到原始问题需要的时间不超过std::reference_wrapper

使用它而不是指针,你很好:

<强> Live On Coliru

#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 << " ";
}

打印

1 1 

答案 5 :(得分:0)

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不是宏,它们是您想要应用的任何条件和转换的占位符)

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
    }
);

答案 6 :(得分:0)

您可以使用copy_if为什么不呢?定义OutputIt(请参阅copy):

struct my_inserter: back_insert_iterator<vector<ha *>>
{
  my_inserter(vector<ha *> &dst)
    : back_insert_iterator<vector<ha *>>(back_inserter<vector<ha *>>(dst))
  {
  }
  my_inserter &operator *()
  {
    return *this;
  }
  my_inserter &operator =(ha &arg)
  {
    *static_cast< back_insert_iterator<vector<ha *>> &>(*this) = &arg;
    return *this;
  }
};

并重写您的代码:

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

    my_inserter yes(ph);
    copy_if(v.begin(), v.end(), yes,
        [](const ha &parg) { return parg.i < 2;  });

    return 0;
}

答案 7 :(得分:0)

这只是问题1的答案“使用可用的C ++标准库工具是否有更优雅的解决方法?”。

如果您可以使用c ++ 17,那么您可以使用std::optional来获得仅使用C ++标准库功能的更简单的解决方案。想法是在没有映射的情况下返回std::nullopt

See live on Coliru

#include <iostream>
#include <optional>
#include <vector>

template <
    class InputIterator, class OutputIterator, 
    class UnaryOperator
>
OutputIterator filter_transform(InputIterator first1, InputIterator last1,
                            OutputIterator result, UnaryOperator op)
{
    while (first1 != last1) 
    {
        if (auto mapped = op(*first1)) {
            *result = std::move(mapped.value());
            ++result;
        }
        ++first1;
    }
    return result;
}

struct ha { 
    int i;
    explicit ha(int a) : i(a) {}
};

int main()
{
    std::vector<ha> v{ ha{1}, ha{7}, ha{1} }; // initial vector

    // GOAL : make a vector of pointers to elements with i < 2
    std::vector<ha*> ph; // target vector
    filter_transform(v.begin(), v.end(), back_inserter(ph), 
        [](ha &arg) { return arg.i < 2 ? std::make_optional(&arg) : std::nullopt; });

    for (auto p : ph)
        std::cout << p->i << std::endl;

    return 0;
}

请注意,我刚刚在C ++中实现了Rust's approach

答案 8 :(得分:0)

您可以使用std::accumulate来操作指向目标容器的指针:

Live On Coliru

#include <numeric>
#include <iostream>
#include <vector>

struct ha
{
    int i;
};

// filter and transform is here
std::vector<int> * fx(std::vector<int> *a, struct ha const & v)
{
    if (v.i < 2)
    {
        a->push_back(v.i);
    }

    return a;
}

int main()
{
    std::vector<ha> v { {1}, {7}, {1}, };

    std::vector<int> ph; // target vector

    std::accumulate(v.begin(), v.end(), &ph, fx);
    
    for (int el : ph)
    {
        std::cout << el << " ";
    }
}

打印

1 1