12

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

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

4

2 回答 2

12

预计会出现过载歧义。

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

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

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

于 2012-02-18T08:21:45.867 回答
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);

这仍然有一个烦人的问题,如果有人传递了一个不满足我们条件的谓词(或实际上任何东西),我们会得到一个“找不到匹配函数”错误(重载解析失败),而不是一个有用的错误。如果你想解决这个问题,你可以添加一个'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" ); }

dependent_false_type必须是例如继承自的类型std::false_type,我们不能static_assert简单地开启或者每次false都会触发,而不仅仅是在实例化模板时,如我们所愿。或者您可以重复我们在此处的条件,它有点像代码中的文档,但不会改进功能本身。)std::enable_if

剩下的就是 where to find is_callable<Functor, Signature>,因为它实际上不是标准特征。void如果您以前曾经编写过 SFINAE 测试,那么实现起来相对容易,但由于您必须部分专注于回报,所以有点乏味。我在这里没有专门化,因为这个答案已经足够长了。

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

于 2012-02-18T13:56:37.733 回答