Here is a simple example program:
using fn_string = function<void(const string&)>;
using fn_optional_string = function<void(const optional<string>&)>;
void foo(fn_string) { cout << "string" << endl; }
void foo(fn_optional_string) { cout << "optional string" << endl; }
int main()
{
foo([&](const string&){ });
foo([&](const optional<string>&){ }); // <-- ambiguous
return 0;
}
It has 2 overloads for foo()
-- one taking function with string
parameter and another with optional<string>
.
Why is 2nd call to foo()
ambiguous?
Is there a simple way to fix it? Without casts?
UPDATE
The above was an overly simplified example of the following real world problem I am trying to solve, which is:
using delegate = variant<
function<void()>,
function<void(const string&)>,
function<void(const optional<string>&)>
>;
struct foo
{
void add_delegate(delegate fn) { fns.push_back(std::move(fn)); }
vector<delegate> fns;
};
int main()
{
foo bar;
bar.add_delegate([&](){ });
bar.add_delegate([&](const string&){ });
bar.add_delegate([&](const optional<string>&){ }); // ERROR
return 0;
}
The last call to add_delegate
fails to compile, as it can't decide between function<void(const string&)>
and function<void(const optional<string>&)>
.
My understanding was that the issue had to do with overload resolution (hence my original example). What change should I make to add_delegate
to permit it to accept all 3 versions of lambdas?
Complete example can be found on Coliru.
A lambda is not a std::function<>
. A std::function<R(Args...)>
is a type-erasure value type that can store any copyable object that is call-compatible with R(Args...)
.
In one case above, R
is void
(which for a std::function
means "I don't care what it returns), and Args...
is std::string
. A callable object is call-compatible with this if you can call it with a std::string
rvalue.
This is true of both std::optional<std::string>
and std::string
.
There is no special overload for "exact match" -- all that matters is, call compatible or not.
There are a few ways to handle this.
template<std::size_t N>
struct overload_order : overload_order<N-1> {};
template<>
struct overload_order<0> {};
namespace details {
void foo(overload_order<1>, fn_string) { cout << "string" << endl; }
void foo(overload_order<0>, fn_optional_string) { cout << "optional string" << endl; }
}
template<class F>
void foo(F&& f) {
foo( overload_order<!std::is_same<std::decay_t<F>, fn_optional_string>{}>{}, std::forward<F>(f) );
}
now we first try the fn_string
one, and only if that fails do we try fn_optional_string
, unless the argument is already a fn_optional_string
, in which case we dispatch directly to that overload.
Declare the argument specifically as a fun_optional_string.
I don't know what to type to keep the software from complaining about a code only answer, so here's a poem:
There is an old hack from Milpitas... His motto, "No bug can defeat us." ... His resolve never lapses ... As he fires those synapses ... Fueled by doughnuts, cold rice, and fajitas. ...
#include <functional>
#include <iostream>
#include <optional>
#include <string>
using namespace std;
using fn_string = function<void(const string&)>;
using fn_optional_string = function<void(const optional<string>&)>;
void foo(fn_string) { cout << "string" << endl; }
void foo(fn_optional_string) { cout << "optional string" << endl; }
int main()
{
foo([&](const string&){ });
fn_optional_string g = [&](const optional<string>&) {};
foo(g); // <-- not ambiguous
return 0;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With