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 |3 Answers
Reset to default 3To 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 typesArgs...
and return typeR
.
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 foo
s:
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.
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:01template <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