通过引用将函数对象传递到std算法 [英] Passing function objects into std algorithms by reference

查看:114
本文介绍了通过引用将函数对象传递到std算法的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

不是最好通过转发引用而不是值通过函数对象到STL算法中?它允许使用传递的函数对象的 operator()的引用限定符。



关于 std :: for_each 算法的问题( 1 , a href =http://stackoverflow.com/questions/2048967/> 2 ),它们正考虑改变传递给的函数对象的可观察状态的问题std :: for_each



通过lvalue leference传递功能对象会解决这个问题,即使对于那些算法,它不能返回功能对象的副作用(由于他们应该返回,说,输出迭代器最后一个值或其他)。



例如,算法 std :: for_each (从libc ++复制):

 模板< typename _InputIterator,typename _Function> 
_Function
for_each(_InputIterator __first,_InputIterator __last,_Function __f)
{
for(; __first!= __last; ++ __ first)
__f ;
return _GLIBCXX_MOVE(__ f);
}

至:

  template< typename _InputIterator,typename _Function> 
_Function&&&
for_each(_InputIterator __first,_InputIterator __last,_Function& __f)
{
for(; __first!= __last; ++ __ first)
_GLIBCXX_FORWARD(_Function,__f) (*__第一);
return _GLIBCXX_FORWARD(_Function,__f);
}

或(如果允许进行此类更改) std :: for_each 可以返回 void ,而不会失去功能。类似的变化(从传递值到通过转移引用的变化以及改变所有调用到调用 std :: forward ed函数对象而不是仅仅非const-lvalue)是可能的对于所有的< numeric> < algorithm> 的算法。



我知道一个部分的解决方法:传递对象,包裹 std :: ref (或 std: :cref 强制执行 const this ),但转发有问题 operator()另一个解决方法是明确指定引用(例如,引用),并将引用传递给另一个引用(引用)。cv-ref-qualifiers从被包装的函数对象到包装器的 operator()



<参数类型转换为算法的模板参数列表,但目前 Function 参数总是紧跟 InputIterator Output Iterator 参数:

  #include< iostream> 
#include< list>
#include< algorithm>
#include #include< utility>

#include< cstdlib>

int main()
{
std :: list< int> 1 {{1,2,3,4}};
std :: copy_n(std :: cbegin(l),l.size(),std :: ostream_iterator< int>(std :: cout,));
std :: cout<< std :: endl;
struct F
{
int state;
void operator()(int i){state + = i; }
} f {0};
using I = std :: list< int> :: const_iterator;
std :: for_each< I,F&& >(std :: cbegin(1),std :: cend(1),std :: move(f));
std :: cout<< f.state<< std :: endl;
return EXIT_SUCCESS;
}

更改将允许传递不可复制和/或

解决方案


不是它更好地通过
将函数对象传递给STL算法,而不是通过值?


是的, 。如果有一个要求,函数不需要 CopyConstructible CopyAssignable MoveConstructible MoveAssignable 。但是标准在25.1中具体说明:


注意:除非另有说明, b对象作为参数允许自由复制这些函数对象
。对象标识重要的程序员应该
考虑使用指向非复制的
实现对象的包装器类,例如 reference_wrapper< T> (20.9。 4),或者一些
的等价解。 ]


这个问题在1998年被认为是 LWG 92 。并且当时添加了上面的注释(注释已被修改为 reference_wrapper< T> 当时不存在)。



这是std :: lib的供应商的一个很好的解决方案,并且是一个很好的决议委员会的成员谁有修复规范的工作,但不是那么多的人,如你想使用有状态的函子。



当然,当时,转发引用不可能作为一个可能的解决方案。此外,在那个时候,std ::实现通常通过算法内的值传递函数,这将进一步破坏它的状态(如 LWG 92



您已正确涉及与此问题相关的所有点:




  • 客户可以使用 std :: ref

  • 客户可以显式指定函子参考参数,但这不会影响参数限定函数。


  • 明确指定函子参考参数对客户端非常不方便,因为它们总是




Fwiw,libc ++是唯一的std ::实现,本身从内部复制函子。也就是说如果您编写了 LWG 92 示例:

  #include< algorithm> 
#include< iostream>
#include< list>
#include< numeric>

template< class C>
void
display(const C& c)
{
std :: cout< '{';
if(!c.empty())
{
auto i = c.begin();
std :: cout<< *一世;
for(++ i; i!= c.end(); ++ i)
std :: cout< ,< *一世;
}
std :: cout<< '}'<< '\\\
';
}

class Nth {//对于第n个元素返回true的函数对象
private:
int nth; // element for return true for
int count; //元素计数器
public:
Nth(int n):nth(n),count(0){
}
bool operator b return ++ count == nth;
}
};

int
main()
{
std :: list< int> coll(10);
std :: iota(coll.begin(),coll.end(),0);
display(coll);
auto pos = std :: remove_if(coll.begin(),coll.end(),Nth {3});
col.erase(pos,coll.end());
display(coll);
}

今天的结果是:



libc ++

  {0,1,2,3,4,5,6 ,7,8,9} 
{0,1,3,4,5,6,7,8,9}

g ++

  {0,1,2,3, 4,5,6,7,8,9} 
{0,1,3,4,6,7,8,9}

VS-2015

  {0,1 ,2,3,4,5,6,7,8,9} 
{0,1,3,4,6,7,8,9}

g ++的libstdc ++和VS-2015仍然正在将



将代码更改为:

$ b $ <$ p $ <$> b

 第N pred {3}; 
auto pos = std :: remove_if(coll.begin(),coll.end(),std :: ref(pred));

会将结果可移植到:



<$ pre> {0,1,2,3,4,5,6,7,8,9}
{0,1,3,4,5,6,7, 8,9}

Imho,这只是一个运行时错误,等待程序员不熟悉具有std :: lib的长历史。


Isn't it better to pass function objects into the STL algorithms by forwarding reference rather then by value? It would allow one to utilize ref-qualifiers of operator () of function objects passed.

There are a couple of questions about std::for_each algorithm on SO (1, 2), which are considering a problem with changing of observable state of function object passed to std::for_each.

Passing functional objects by lvalue leference would solve the problem as a side effect even for those of algorithms, which can't return functional object (due to they should return, say, output iterator last value or something else).

For example the algorithm std::for_each can be changed from (copied from libc++):

template<typename _InputIterator, typename _Function>
_Function
for_each(_InputIterator __first, _InputIterator __last, _Function __f)
{
  for (; __first != __last; ++__first)
    __f(*__first);
  return _GLIBCXX_MOVE(__f);
}

to:

template<typename _InputIterator, typename _Function>
_Function &&
for_each(_InputIterator __first, _InputIterator __last, _Function && __f)
{
  for (; __first != __last; ++__first)
    _GLIBCXX_FORWARD(_Function, __f)(*__first);
  return _GLIBCXX_FORWARD(_Function, __f);
}

or (if such breaking changing is allowed) std::for_each can return void without loss of functionality. Similar changes (change from passing by value to passing by forwarding reference and change all invocations to calling std::forwarded function object instead of just non-const-lvalue) are possible for all the rest <numeric>'s and <algorithm>'s algorithms.

I know a partial workaround: is to pass object, wrapped by std::ref (or std::cref to enforce const this), but there are issues with forwarding operator () cv-ref-qualifiers from wrapped functional object to wrapper's operator ().

Another workaround is to explicitly specify reference argument type into alorithm's template parameter list, but currently Function parameter sadly always follows the InputIterator and OutputIterator parameters in the list:

#include <iostream>
#include <list>
#include <algorithm>
#include <iterator>
#include <utility>

#include <cstdlib>

int main()
{
    std::list< int > l{{1, 2, 3, 4}};
    std::copy_n(std::cbegin(l), l.size(), std::ostream_iterator< int >(std::cout, " "));
    std::cout << std::endl;
    struct F
    {
        int state;
        void operator () (int i) { state += i; }
    } f{0};
    using I = std::list< int >::const_iterator;
    std::for_each< I, F && >(std::cbegin(l), std::cend(l), std::move(f));
    std::cout << f.state << std::endl;
    return EXIT_SUCCESS;
}

By the way the change would allow to pass a non-copyable and/or non-moveable function objects to the algorithms w/o wrapping them.

解决方案

Isn't it better to pass function objects into the STL algorithms by forwarding reference rather then by value?

Yes, it would be better. And it would be better if there were a requirement that the functor need not be CopyConstructible, CopyAssignable, MoveConstructible or MoveAssignable. However the standard specifically says in 25.1:

Note: Unless otherwise specified, algorithms that take function objects as arguments are permitted to copy those function objects freely. Programmers for whom object identity is important should consider using a wrapper class that points to a noncopied implementation object such as reference_wrapper<T> (20.9.4), or some equivalent solution. — end note]

This issue was considered all the way back in 1998 as LWG 92. And at that time the Note I quote above was added (the Note has since been modified as reference_wrapper<T> didn't exist at the time).

This was a good resolution for vendors of the std::lib, and a good resolution for members of the committee who had the job of fixing the specification, but not so much for people such as yourself wanting to use stateful functors.

And of course, at that time, forwarding references weren't available as a possible solution. Also at that time, it was common for std::implementations to pass the functor around by value within an algorithm, which would further destroy its state (as demonstrated in the description of LWG 92.

You have correctly touched upon all of the points connected to this issue:

  • Clients can use std::ref instead, but this won't respect reference-qualified functors.

  • Clients can explicitly specify functor reference parameters, but this won't prohibit implementations from copying the functor within the algorithm.

  • Explicitly specifying functor reference parameters is extremely inconvenient for the client since they are always ordered last in the template parameter list.

Fwiw, libc++ is the only std::implementation that was written which forbade itself from internally copying functors. I.e. if you code up the LWG 92 example:

#include <algorithm>
#include <iostream>
#include <list>
#include <numeric>

template <class C>
void
display(const C& c)
{
    std::cout << '{';
    if (!c.empty())
    {
        auto i = c.begin();
        std::cout << *i;
        for (++i; i != c.end(); ++i)
            std::cout << ", " << *i;
    }
    std::cout << '}' << '\n';
}

class Nth {    // function object that returns true for the nth element 
  private: 
    int nth;     // element to return true for 
    int count;   // element counter 
  public: 
    Nth (int n) : nth(n), count(0) { 
    } 
    bool operator() (int) { 
        return ++count == nth; 
    } 
};

int
main()
{
    std::list<int> coll(10);
    std::iota(coll.begin(), coll.end(), 0);
    display(coll);
    auto pos = std::remove_if(coll.begin(), coll.end(), Nth{3});
    coll.erase(pos, coll.end());
    display(coll);
}

The results today are:

libc++

{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
{0, 1, 3, 4, 5, 6, 7, 8, 9}

g++

{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
{0, 1, 3, 4, 6, 7, 8, 9}

VS-2015

{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
{0, 1, 3, 4, 6, 7, 8, 9}

g++'s libstdc++ and VS-2015 are still copying Nth internal to remove_if just as described 18 years ago by Nico Josuttis.

Changing the code to:

    Nth pred{3};
    auto pos = std::remove_if(coll.begin(), coll.end(), std::ref(pred));

does portably change the results to:

{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
{0, 1, 3, 4, 5, 6, 7, 8, 9}

Imho, this is just a run-time error waiting to happen to programmers not familiar with the long history of the std::lib.

这篇关于通过引用将函数对象传递到std算法的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆