想象一下,我们遇到以下情况:
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;
}
对std::copy_if
的调用都会产生编译错误,因为编译器无法正确重载predicate()
函数,因为std::copy_if
模板签名接受任何类型的谓词: / p>
template<typename _IIter,
typename _OIter,
typename _Predicate>
_OIter copy_if( // etc...
如果我将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 );
}
我的问题是:为什么在STL中它不像这样受到限制?根据我所看到的,如果_Predicate
类型不是返回bool
并接受迭代输入类型的函数,则无论如何都会生成编译器错误。那么为什么不将这个约束放在签名中,以便重载解析可以工作呢?
答案 0 :(得分:12)
因为谓词不一定是函数,但它也可以是一个函子。限制函子类型几乎是不可能的,因为它只要有operator()
定义就可以是任何东西。
实际上我建议你在这里将重载函数转换为多态函数:
struct predicate {
bool operator()( const A& a) const
{
return a.i > 123;
}
bool operator()( const B& b) const
{
return operator()(b.a);
}
}
并使用实例调用仿函数,即
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 ()
然后在算法中选择正确的重载。
答案 1 :(得分:4)
此问题不仅影响算法的谓词。它发生在模板类型推导推导出重载函数的任何地方。模板类型推导在重载解析之前发生,因此编译器缺少用于解决模糊性的上下文信息。
正确编写的约束可能非常复杂,因为它需要考虑参数和返回类型转换,绑定,lambda,仿函数,mem_fn
等等。
解决模糊性(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)); });
这会延迟重载解析直到模板类型扣除后。
如果我拒绝(或我的老板拒绝)升级到c ++ 14
,该怎么办?
然后手动滚动相同的lambda:
struct predicate_caller
{
template<class T>
decltype(auto) operator()(T&& t) const
{
return predicate(std::forward<T>(t));
}
};
并且这样打电话:
std::copy_if(b_source.begin(), b_source.end(),
std::back_inserter( b_target ),
predicate_caller());