通过引用将函数对象传递给std算法

时间:2016-01-16 09:42:09

标签: c++ algorithm c++11 stl c++14

通过转发引用而不是按值传递函数对象到STL算法是否更好?它允许一个人使用传递的operator ()函数对象的ref-qualifiers。

关于SO {12)的std::for_each算法存在一些问题,这些问题正在考虑更改传递给{{1的函数对象的可观察状态的问题}}。

通过左值传递传递函数对象会将问题解决为副作用,即使对于那些无法返回功能对象的算法(因为它们应该返回,比如输出迭代器或其他值)。

例如,可以更改算法std::for_each(从libc ++复制):

std::for_each

为:

template<typename _InputIterator, typename _Function>
_Function
for_each(_InputIterator __first, _InputIterator __last, _Function __f)
{
  for (; __first != __last; ++__first)
    __f(*__first);
  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)(*__first); return _GLIBCXX_FORWARD(_Function, __f); } 可以返回std::for_each而不会丢失功能。所有其余的void&#39都可以进行类似的更改(从传递值到传递转发引用并将所有调用更改为调用std::forward ed函数对象而不仅仅是非const-lvalue) ; s和<numeric>的算法。

我知道部分解决方法:传递对象,由<algorithm>包裹(或std::ref强制执行std::cref),但转发const this cv-存在问题从包装函数对象的ref-qualifiers到包装器operator ()

另一种解决方法是将参数参数类型显式指定到算法的模板参数列表中,但目前operator ()参数始终遵循列表中的FunctionInputIterator参数:

OutputIterator

顺便说一下,更改将允许将不可复制和/或不可移动的函数对象传递给不包装它们的算法。

1 个答案:

答案 0 :(得分:12)

  

最好将函数对象传递给STL算法   转发参考而不是价值?

是的,会更好。如果要求函子不需要CopyConstructibleCopyAssignableMoveConstructibleMoveAssignable,那会更好。但是标准在25.1中明确指出:

  

注意:除非另有说明,否则算法采取功能   允许对象作为参数复制这些函数对象   自如。对象身份很重要的程序员应该   考虑使用指向非复制的包装类   实现对象,例如reference_wrapper<T>(20.9.4),或者某些   等效解决方案 - 结束记录]

这个问题在1998年被视为LWG 92。在那个时候,上面引用的注释I被添加了(注释已被修改,因为reference_wrapper<T>当时不存在。)

对于std :: lib的供应商来说,这是一个很好的解决方案,对于那些负责修改规范的委员会成员来说,这是一个很好的解决方案,但对于像你这样想要使用有状态函子的人来说则不是那么好。

当然,在那个时候,转发参考作为可能的解决方案并不可用。同样在那时,std :: implementation通常在算法中通过值传递仿函数,这将进一步破坏其状态(如LWG 92的描述中所示)。

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

  • 客户可以使用std::ref代替,但这不会尊重参考资格的仿函数。

  • 客户端可以明确指定仿函数参考参数,但这不会禁止实现在算法 中复制仿函数

  • 明确指定仿函数参考参数对于客户来说非常不方便,因为它们总是在模板参数列表的最后排序。

Fwiw,libc ++是唯一编写的std :: implementation,它禁止内部复制仿函数。即如果您编写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 << *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);
}

今天的结果是:

<强>的libc ++

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

<强>克++

{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}
正如18年前Nico Josuttis所描述的那样,g ++的libstdc ++和VS-2015 复制Nth内部remove_if

将代码更改为:

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

可以将结果可移植地更改为:

{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
{0, 1, 3, 4, 5, 6, 7, 8, 9}
Imho,这只是一个运行时错误等待发生在不熟悉std :: lib的悠久历史的程序员身上。