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

发布于 2025-01-06 10:20:52 字数 2905 浏览 4 评论 0原文

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

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

I've got an function that iterates over a container and passes each element to a predicate for filtering. An overload of this function also passes the index of each element into the predicate.

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);

I have found that attempting to call either of these functions with a naked lambda will cause a compiler error in VC11, while using a std::function object will succeed:

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>          ]

Is this to be expected? Is there a different way to overload these functions (short of renaming one to be "DoSomethingIfWithIndex"?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

安人多梦 2025-01-13 10:20:52

过载歧义是预期的。

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

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

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

The overload ambiguity is expected.

std::function has a converting constructor template that accepts any argument. Only after the constructor template is instantiated can the compiler determine that it will reject the argument.

In both your first and second examples, a user-defined conversion is required to convert the unspecified lambda type to each of the std::function types. Neither conversion is better (they are both user-defined conversions), so the compiler reports the overload ambiguity.

In your third example (the one that works), there is no ambiguity because the std::function constructor template is not used. Instead, its copy constructor is used (and, all other things being equal, nontemplates are preferred over templates).

忘年祭陌 2025-01-13 10:20:52

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 的类型,我们可以't static_assert 简单地 false 或将每次触发,而不是像我们希望的那样仅在实例化模板时触发。你可以重复我们的条件在 std::enable_if 内部,它在某种程度上作为代码内部的文档,但并没有改进功能本身。)

剩下的就是在哪里找到 is_callable< /code>,因为它实际上不是标准特征。如果您以前编写过 SFINAE 测试,那么实现起来相对容易,但有点乏味,因为您必须部分专门针对 void 返回。我不会在这里进行专门讨论,因为这个答案已经足够长了。

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

std::function has its uses at binary delimitations, but not as a general-use parameter for functors. As you've just discovered, its converting constructor interacts badly with overload resolution (and that has nothing to do with lambda expressions). Since DoSomethingIf is already a template, I don't see a problem with the canonical solution of accepting generalized functors:

template<typename TContainer, typename Predicate>
void DoSomethingIf(TContainer& c, Predicate&& predicate);

As you may notice though, this version can't be overloaded and simply will accept anything as a predicate, even int. Overloading is easily solved with SFINAE, as usual:

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);

This still has the annoying issue that if someone passes a predicate (or really anything) that doesn't satisfy our conditions, we get a 'no matching function found' error (an overload resolution failure) rather than a helpful error. If you want to solve that, you can add a 'catch-all' overload:

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 just has to be e.g. a type inheriting from std::false_type, we can't static_assert on simply false or that will be triggered every time, not just when the template is instantiated, as we'd like. Alternatively you can repeat the condition we have inside std::enable_if here, which works somewhat as documentation inside the code, but doesn't improve the functionality itself.)

All that remains is where to find is_callable<Functor, Signature>, as it's not in fact a Standard trait. It's relatively easy to implement if you've ever written an SFINAE test before, but a bit tedious as you have to partially specialize for void returns. I'm not putting a specialization here as this answer is long enough as it is.

If you find this solution powerful but too verbose, then perhaps you'd enjoy concepts :)

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文