我有一个迭代容器的函数,并将每个元素传递给谓词进行过滤。此函数的重载也会将每个元素的索引传递给谓词。
template<typename TContainer>
void DoSomethingIf(TContainer &c, std::function<bool (const typename TContainer::const_reference)> predicate);
template<typename TContainer>
void DoSomethingIf(TContainer &c, std::function<bool (const typename TContainer::const_reference, int)> predicate);
我发现尝试使用裸lambda调用 这些函数将导致VC11中的编译器错误,而使用std :: function对象将成功:
void foo()
{
std::vector<int> v;
// fails
DoSomethingIf(v, [](const int &x) { return x == 0; });
// also fails
auto lambda = [](const int &x) { return x == 0; };
DoSomethingIf(v, lambda);
// success!
std::function<bool (const int &)> fn = [](const int &x) { return x == 0; };
DoSomethingIf(v, fn);
}
1>c:\users\moswald\test.cpp(15): error C2668: 'DoSomethingIf' : ambiguous call to overloaded function
1> c:\users\moswald\test.cpp(8): could be 'void DoSomethingIf<std::vector<_Ty>>(TContainer &,std::function<_Fty>)'
1> with
1> [
1> _Ty=int,
1> TContainer=std::vector<int>,
1> _Fty=bool (const int &,int)
1> ]
1> c:\users\moswald\test.cpp(5): or 'void DoSomethingIf<std::vector<_Ty>>(TContainer &,std::function<_Fty>)'
1> with
1> [
1> _Ty=int,
1> TContainer=std::vector<int>,
1> _Fty=bool (const int &)
1> ]
1> while trying to match the argument list '(std::vector<_Ty>, foo::<lambda_8EADDE04A8D35A3C>)'
1> with
1> [
1> _Ty=int
1> ]
1>c:\users\moswald\test.cpp(19): error C2668: 'DoSomethingIf' : ambiguous call to overloaded function
1> c:\users\moswald\test.cpp(8): could be 'void DoSomethingIf<std::vector<_Ty>>(TContainer &,std::function<_Fty>)'
1> with
1> [
1> _Ty=int,
1> TContainer=std::vector<int>,
1> _Fty=bool (const int &,int)
1> ]
1> c:\users\moswald\test.cpp(5): or 'void DoSomethingIf<std::vector<_Ty>>(TContainer &,std::function<_Fty>)'
1> with
1> [
1> _Ty=int,
1> TContainer=std::vector<int>,
1> _Fty=bool (const int &)
1> ]
1> while trying to match the argument list '(std::vector<_Ty>, foo::<lambda_8EADDE04A8D35A3D>)'
1> with
1> [
1> _Ty=int
1> ]
这是预期的吗?是否有不同的方法来重载这些函数(没有重命名为“DoSomethingIfWithIndex
”?
答案 0 :(得分:12)
预计会出现过载歧义。
std::function
有一个转换构造函数模板,可以接受任何参数。只有在实例化构造函数模板之后,编译器才能确定它将拒绝该参数。
在第一个和第二个示例中,需要进行用户定义的转换,以将未指定的lambda类型转换为每个std::function
类型。转换都不是更好(它们都是用户定义的转换),因此编译器会报告过载歧义。
在您的第三个示例(有效的示例)中,没有歧义,因为未使用std::function
构造函数模板。相反,使用其复制构造函数(并且,在所有其他条件相同的情况下,非模板优先于模板)。
答案 1 :(得分:5)
std::function
用于二进制分隔,但不作为仿函数的通用参数。正如您刚刚发现的那样,它的转换构造函数与重载决策交互严重(这与lambda表达式无关)。由于DoSomethingIf
已经已经一个模板,我认为接受广义仿函数的规范解决方案没有问题:
template<typename TContainer, typename Predicate>
void DoSomethingIf(TContainer& c, Predicate&& predicate);
正如您可能注意到的那样,此版本无法重载,只会接受任何谓词作为谓词,甚至int
。像往常一样,使用SFINAE可以轻松解决重载问题:
template<
typename Container
, typename Predicate
, typename = typename std::enable_if<
is_callable<Predicate, bool(typename Container::const_reference)>::value
>::type
>
void
DoSomethingIf(Container& container, Predicate&& predicate);
template<
typename Container
, typename Predicate
, typename = typename std::enable_if<
is_callable<Predicate, bool(typename Container::const_reference, int)>::value
>::type
// dummy parameter to disambiguate this definition from the previous one
, typename = void
>
void
DoSomethingIf(Container& container, Predicate&& predicate);
这仍然有一个恼人的问题,如果有人传递了一个不符合我们条件的谓词(或任何事情),我们会得到一个'找不到匹配函数'错误(重载解析失败)而不是有用的错误。如果你想解决这个问题,你可以添加一个'全能'过载:
template<
typename Container
, typename Predicate
, typename = typename std::enable_if<
!is_callable<Predicate, bool(typename Container::const_reference)>::value
&& !is_callable<Predicate, bool(typename Container::const_reference, int)>::value
>::type
// more dummies
, typename = void, typename = void
>
void DoSomethingIf(Container&, Predicate&&)
{ static_assert( dependent_false_type<Container>::value,
"Put useful error message here" ); }
(dependent_false_type
只需要是一个继承自std::false_type
的类型,我们不能static_assert
只是false
或每次都会触发 ,而不仅仅是在我们想要实例化模板的时候。或者你可以在这里重复我们在std::enable_if
里面的条件,这在某种程度上可以作为代码中的文档,但不会改进功能本身。)
剩下的就是找到is_callable<Functor, Signature>
的地方,因为它实际上并不是标准特征。如果您之前曾编写过SFINAE测试,则相对容易实现,但由于您必须部分专注于void
返回,因此有点单调乏味。我没有在这里进行专业化,因为这个答案已经足够长了。
如果您发现此解决方案功能强大但过于冗长,那么您可能会喜欢这些概念:)