C ++ lambdas没有正确选择重载函数吗?

时间:2012-02-18 08:16:28

标签: c++ visual-c++ lambda c++11 visual-c++-2012

我有一个迭代容器的函数,并将每个元素传递给谓词进行过滤。此函数的重载也会将每个元素的索引传递给谓词。

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”?

2 个答案:

答案 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返回,因此有点单调乏味。我没有在这里进行专业化,因为这个答案已经足够长了。

如果您发现此解决方案功能强大但过于冗长,那么您可能会喜欢这些概念:)