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

mos*_*ald 12 c++ lambda visual-c++ 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);
Run Code Online (Sandbox Code Playgroud)

我发现尝试使用裸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>          ]
Run Code Online (Sandbox Code Playgroud)

这是预期的吗?有没有不同的方法来重载这些功能(没有重命名一个是" DoSomethingIfWithIndex"?

Jam*_*lis 12

预计过载模糊.

std::function有一个转换构造函数模板,接受任何参数.只有在实例化构造函数模板之后,编译器才能确定它将拒绝该参数.

在第一个和第二个示例中,需要进行用户定义的转换,以将未指定的lambda类型转换为每个std::function类型.转换都不是更好(它们都是用户定义的转换),因此编译器报告过载歧义.

在您的第三个示例(有效的示例)中,没有歧义,因为std::function未使用构造函数模板.相反,使用其复制构造函数(并且,在所有其他条件相同的情况下,非模板优先于模板).


Luc*_*ton 5

std::function它在二进制分隔中有用,但不是作为仿函数的通用参数.正如您刚刚发现的那样,它的转换构造函数与重载决策交互严重(这与lambda表达式无关).既然DoSomethingIf已经一个模板,我看不出有接受广义函子的规范解决的问题:

template<typename TContainer, typename Predicate>
void DoSomethingIf(TContainer& c, Predicate&& predicate);
Run Code Online (Sandbox Code Playgroud)

正如您可能注意到的那样,此版本无法重载,甚至可以接受任何谓词作为谓词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);
Run Code Online (Sandbox Code Playgroud)

这仍然有一个恼人的问题,如果有人传递了一个不满足我们条件的谓词(或任何东西),我们得到一个'找不到匹配函数'错误(重载解析失败)而不是有用的错误.如果你想解决这个问题,你可以添加一个'catch-all'重载:

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" ); }
Run Code Online (Sandbox Code Playgroud)

(dependent_false_type只需要是例如继承自的类型std::false_type,我们不能static_assert简单地false或者每次都会触发,而不仅仅是在模板被实例化时,就像我们想要的那样.或者你可以重复我们里面的条件std::enable_if,这有点像代码中的文档,但不会改进功能本身.)

剩下的就是在哪里找到is_callable<Functor, Signature>,因为它实际上并不是标准特征.如果您之前曾经编写过SFINAE测试,那么实施起来相对容易,但由于您必须部分专注于void返回,因此有点单调乏味.我没有在这里进行专业化,因为这个答案足够长.

如果你发现这个解决方案功能强大但又过于冗长,那么也许你会喜欢这些概念:)