简体   繁体   English

为什么std :: copy_if签名不限制谓词类型

[英]Why does the std::copy_if signature not constrain the predicate type

Imagine we have the following situation: 想象一下,我们有以下情况:

struct A
{
    int i;
};

struct B
{
    A a;
    int other_things;
};

bool predicate( const A& a)
{
    return a.i > 123;
}

bool predicate( const B& b)
{
    return predicate(b.a);
}

int main()
{
    std::vector< A > a_source;
    std::vector< B > b_source;

    std::vector< A > a_target;
    std::vector< B > b_target;

    std::copy_if(a_source.begin(), a_source.end(), std::back_inserter( a_target ), predicate);
    std::copy_if(b_source.begin(), b_source.end(), std::back_inserter( b_target ), predicate);

    return 0;
}

Both the call to std::copy_if generate a compile error, because the correct overload of predicate() function cannot be infered by the compiler since the std::copy_if template signature accepts any type of predicate: std::copy_if的调用都会产生编译错误,因为编译器无法正确重载predicate()函数的正确重载,因为std::copy_if模板签名接受任何类型的谓词:

template<typename _IIter, 
         typename _OIter, 
         typename _Predicate>
_OIter copy_if( // etc...

I found the overload resolution working if I wrap the std::copy_if call into a more constrained template function: 如果我将std::copy_if调用包装到更受约束的模板函数中,我发现重载std::copy_if有效:

template<typename _IIter, 
         typename _OIter, 
         typename _Predicate = bool( const typename std::iterator_traits<_IIter>::value_type& ) >
void copy_if( _IIter source_begin, 
              _IIter source_end, 
              _OIter target,  
              _Predicate pred)
{
    std::copy_if( source_begin, source_end, target, pred );
} 

My question is: why in the STL is it not already constrained like this? 我的问题是:为什么在STL中它不像这样受到限制? From what I've seen, if the _Predicate type is not a function that returns bool and accepts the iterated input type, it is going to generate a compiler error anyway. 从我所看到的情况来看,如果_Predicate类型不是返回bool并接受迭代输入类型的函数,那么无论如何它都会产生编译器错误。 So why not putting this constrain already in the signature, so that overload resolution can work? 那么为什么不将这个约束放在签名中,以便重载解析可以工作呢?

Because the predicate does not have to be a function, but it can be a functor too. 因为谓词不一定是函数,但它也可以是函子。 And restricting functor type is close to impossible since it can be anything at all as long as it has operator() defined. 并且限制函子类型几乎是不可能的,因为只要它定义了operator()它就可以是任何东西。

Actually I suggest you convert the overloaded function to a polymorphic functor here: 实际上我建议你在这里将重载函数转换为多态函数:

struct predicate {
    bool operator()( const A& a) const
    {
        return a.i > 123;
    }

    bool operator()( const B& b) const
    {
        return operator()(b.a);
    }
}

and call the functor with an instance, ie 并使用实例调用仿函数,即

std::copy_if(a_source.begin(), a_source.end(), std::back_inserter( a_target ), predicate());
std::copy_if(b_source.begin(), b_source.end(), std::back_inserter( b_target ), predicate());
//                                                                                      ^^ here, see the ()

Then the correct overload will be selected inside the algorithm. 然后在算法内选择正确的重载。

This problem does not only affect predicates to algorithms. 此问题不仅影响算法的谓词。 It occurs anywhere that template type deduction deduces an overloaded function. 它发生在模板类型推导推导出重载函数的任何地方。 Template type deduction happens before overload resolution, so the compiler lacks the contextual information to resolve the ambiguity. 模板类型推导在重载解析之前发生,因此编译器缺少用于解决模糊性的上下文信息。

The correctly written constraint would be hideously complex, as it would need to take into account argument and return type conversions, binds, lambdas, functors, mem_fn s and so on. 正确编写的约束可能非常复杂,因为它需要考虑参数和返回类型转换,绑定,lambda,functor, mem_fn等等。

An easy way to solve the ambiguity (IMHO) is to call the predicate through a lambda. 解决歧义(IMHO)的一种简单方法是通过lambda调用谓词。

std::copy_if(a_source.begin(), a_source.end(), 
         std::back_inserter( a_target ), 
         [](auto&& x){ return predicate(std::forward<decltype(x)>(x)); });

This defers overload resolution until after template type deduction. 这推迟了重载分辨率,直到模板类型推断之后。

What if I refuse (or my boss refuses) to upgrade to c++14 如果我拒绝(或我的老板拒绝)升级到c ++ 14怎么办?

Then hand-roll the same lambda: 然后手动滚动相同的lambda:

struct predicate_caller
{
  template<class T>
  decltype(auto) operator()(T&& t) const 
  {
    return predicate(std::forward<T>(t));
  }
};

and call like so: 并且这样打电话:

std::copy_if(b_source.begin(), b_source.end(), 
             std::back_inserter( b_target ), 
             predicate_caller());

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM