Ori*_*ent 10 c++ algorithm stl c++11 c++14
通过转发引用而不是通过值将函数对象传递给STL算法不是更好吗?它将允许一个人利用operator ()传递的函数对象的ref限定符.
有几个约问题std::for_each算法SO(1,2),这是考虑与传递给函数对象的可观察到的状态的改变的问题std::for_each.
通过左值传递传递函数对象会将问题解决为副作用,即使对于那些不能返回功能对象的算法(因为它们应该返回,比如输出迭代器或其他值).
例如,std::for_each可以从(从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);
}
Run Code Online (Sandbox Code Playgroud)
至:
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);
}
Run Code Online (Sandbox Code Playgroud)
或者(如果允许这种改变)std::for_each可以返回void而不会丧失功能.std::forward对于所有其他算法<numeric>和<algorithm>算法,类似的更改(从传递值到传递转发引用并将所有调用更改为调用ed函数对象而不仅仅是非const-lvalue)都是可能的.
我知道一个部分的解决方法:是传递对象,包裹std::ref(或std::cref强制执行const this),但是有问题将operator ()cv-ref-qualifiers从包装的功能对象转发到包装器operator ().
另一种解决方法是将参数参数类型显式指定到alorithm的模板参数列表中,但是当前Function参数遗憾地始终遵循列表中的InputIterator和OutputIterator参数:
#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;
}
Run Code Online (Sandbox Code Playgroud)
顺便说一下,改变将允许将不可复制和/或不可移动的函数对象传递给不包装它们的算法.
How*_*ant 12
通过转发引用而不是通过值将函数对象传递给STL算法不是更好吗?
是的,会更好.如果有迹象表明仿函数不必要求它会更好CopyConstructible,CopyAssignable,MoveConstructible或MoveAssignable.但是标准在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);
}
Run Code Online (Sandbox Code Playgroud)
今天的结果是:
的libc ++
{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
{0, 1, 3, 4, 5, 6, 7, 8, 9}
Run Code Online (Sandbox Code Playgroud)
克++
{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
{0, 1, 3, 4, 6, 7, 8, 9}
Run Code Online (Sandbox Code Playgroud)
VS-2015
{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
{0, 1, 3, 4, 6, 7, 8, 9}
Run Code Online (Sandbox Code Playgroud)
正如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));
Run Code Online (Sandbox Code Playgroud)
可以将结果可移植地更改为:
{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
{0, 1, 3, 4, 5, 6, 7, 8, 9}
Run Code Online (Sandbox Code Playgroud)
Imho,这只是一个运行时错误等待发生在不熟悉std :: lib的悠久历史的程序员身上.
| 归档时间: |
|
| 查看次数: |
718 次 |
| 最近记录: |