最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

c++ - std::function can't pick between overloads with polymorphic argument type - Stack Overflow

programmeradmin1浏览0评论

The following functions have all a different signature. I would expect the functions which have a callable which again has a Base& and one that has a Derived& to be different. Although they are pretty similar types, I thought that the compiler could still differentiate between them.

Apparently the compiler can decide which overload to call when the argument is a function pointer, as it has no problem with calling the bar function. However when calling foo, the compiler suddenly thinks that those overloads are ambiguous.

#include <functional>

struct Base {};
struct Derived : Base {};

void foo(std::function<void(Base&)>) {}
void foo(std::function<void(Derived&)>) {}
void bar(void(*)(Base&)) {}
void bar(void(*)(Derived&)) {}

int main() {
    foo([](Base&) {});
    foo([](Derived&) {});
    bar([](Base&){});
    bar([](Derived&){});
}
<source>: In function 'int main()':
<source>:12:8: error: call of overloaded 'foo(main()::<lambda(Base&)>)' is ambiguous
   12 |     foo([](Base&) {});
      |     ~~~^~~~~~~~~~~~~~
<source>:6:6: note: candidate: 'void foo(std::function<void(Base&)>)'
    6 | void foo(std::function<void(Base&)>) {}
      |      ^~~
<source>:7:6: note: candidate: 'void foo(std::function<void(Derived&)>)'
    7 | void foo(std::function<void(Derived&)>) {}
      |      ^~~
Compiler returned: 1

Why is that and how can I fix it? I know that I could just do something like

foo(std::function<void(Base&)>([](Base&) {}));

but that gets ugly and annoying quick and I don't want to make any changes on the calling site.

The following functions have all a different signature. I would expect the functions which have a callable which again has a Base& and one that has a Derived& to be different. Although they are pretty similar types, I thought that the compiler could still differentiate between them.

Apparently the compiler can decide which overload to call when the argument is a function pointer, as it has no problem with calling the bar function. However when calling foo, the compiler suddenly thinks that those overloads are ambiguous.

#include <functional>

struct Base {};
struct Derived : Base {};

void foo(std::function<void(Base&)>) {}
void foo(std::function<void(Derived&)>) {}
void bar(void(*)(Base&)) {}
void bar(void(*)(Derived&)) {}

int main() {
    foo([](Base&) {});
    foo([](Derived&) {});
    bar([](Base&){});
    bar([](Derived&){});
}
<source>: In function 'int main()':
<source>:12:8: error: call of overloaded 'foo(main()::<lambda(Base&)>)' is ambiguous
   12 |     foo([](Base&) {});
      |     ~~~^~~~~~~~~~~~~~
<source>:6:6: note: candidate: 'void foo(std::function<void(Base&)>)'
    6 | void foo(std::function<void(Base&)>) {}
      |      ^~~
<source>:7:6: note: candidate: 'void foo(std::function<void(Derived&)>)'
    7 | void foo(std::function<void(Derived&)>) {}
      |      ^~~
Compiler returned: 1

Why is that and how can I fix it? I know that I could just do something like

foo(std::function<void(Base&)>([](Base&) {}));

but that gets ugly and annoying quick and I don't want to make any changes on the calling site.

Share Improve this question asked Nov 20, 2024 at 18:49 JoelJoel 1,6884 silver badges22 bronze badges 2
  • 1 it is in fact ambiguous, neither overload is a perfect match (because lambdas are not std::function but are convertible to one ... or in this case both). a good question is how to disambiguate it without changing the call site – Ahmed AEK Commented Nov 20, 2024 at 19:01
  • 2 template <class F> requires std::is_same_v<decltype(std::function(std::declval<F>())), std::function<void(Derived&)>> void foo(F&&) {} wouldn't require changing the call site :-) – Ted Lyngmo Commented Nov 20, 2024 at 19:02
Add a comment  | 

3 Answers 3

Reset to default 3

To disambiguate, one solution is to add one overload, and force the compiler to perform overload resolution using std::function types only, instead of converting from lambda to std::function.

The reason why your call is ambiguous is that the two foo function calls requires a conversion to std::function, whereas the function pointer are not convertible from one to another.

Simply add that overload:

void foo(std::convertible_to<std::function<void(Base&)>> auto&& fn)
requires (
  // This function don't accept things that already are std::function
  not std::same_as<decltype(fn), decltype(std::function{fn})>
) {
  foo(std::function{fn});
}

This solution has the advantage that everything will still work as you add more and more overloads, and no changes are required to the additional overload since it only uses Base to disambiguate. There's also no change on the other overload, this solution is a pure addition.

It's also still possible to call foo with a lambda that has different reference types:

foo([](Derived const&) {}); // works

Looking at the assembly generated by this live example, you can see that it's exactly as expected.

It is ambiguous because of the function constructor

template< class F >
function( F&& f );

which participates in overload resolution only if

  • An lvalue of type std::decay<F>::type is callable for argument types Args... and return type R.

That is in fact true. A void(Base&) can be called with a reference to an object of a derived class and the return type, R, is also void in your supplied lambda.

You could solve it in different ways. If you want separate overloads, you could add constraits to one or both of your current foos:

template <class F>
    requires std::is_same_v<decltype(std::function(std::declval<F>())),
                            std::function<void(Base&)>>
void foo(F&& f) {
}

template <class F>
    requires std::is_same_v<decltype(std::function(std::declval<F>())),
                            std::function<void(Derived&)>>
void foo(F&& f) {
}

Note: f inside the functions here is not a std::function<void(Base&)> or std::function<void(Derived&)> but of the same type as the lambda you provided. It can be called, f(obj);, or put in a std::function if you please though. If you don't need to store it, then calling it directly is cheaper than putting it in a std::function first.

It's ambiguous because std::function allows you to forward its parameters to the type-erased callable that it's initialized with. As long as the function you pass can accept the parameters of the function type in std::function, it's considered viable. In this case, std::function<void(Base&)> and std::function<void(Derived&)> are both viable in the case of foo([](Base&) {}), because a Base& can bind to both an lvalue of type Base and an lvalue of type Derived.

More specifically, the calls to foo() invoke the converting-constructor template<class F> std::function<T(Args...)>::function( F&& f ) which only participates in overload resolution if f is callable with arguments of type Args....

bar() doesn't cause any issues with ambiguity because the lambda can be converted to the exact function pointer type, and it's not possible to convert a function pointer based on parameter types.

发布评论

评论列表(0)

  1. 暂无评论