Why doesn't std::function participate in overload resolution?
This doesn't really have anything to do with "phases of translation". It's purely about the constructors of std::function
.
See, std::function<R(Args)>
doesn't require that the given function is exactly of the type R(Args)
. In particular, it doesn't require that it is given a function pointer. It can take any callable type (member function pointer, some object that has an overload of operator()
) so long as it is invokable as if it took Args
parameters and returns something convertible to R
(or if R
is void
, it can return anything).
To do that, the appropriate constructor of std::function
must be a template: template<typename F> function(F f);
. That is, it can take any function type (subject to the above restrictions).
The expression baz
represents an overload set. If you use that expression to call the overload set, that's fine. If you use that expression as a parameter to a function that takes a specific function pointer, C++ can whittle down the overload set to a single call, thus making it fine.
However, once a function is a template, and you're using template argument deduction to figure out what that parameter is, C++ no longer has the ability to determine what the correct overload in the overload set is. So you must specify it directly.
Overload resolution occurs only when (a) you are calling the name of a function/operator, or (b) are casting it to a pointer (to function or member function) with an explicit signature.
Neither is occurring here.
std::function
takes any object that is compatible with its signature. It doesn't take a function pointer specifically. (a lambda is not a std function, and a std function is not a lambda)
Now in my homebrew function variants, for signature R(Args...)
I also accept a R(*)(Args...)
argument (an exact match) for exactly this reason. But it means that it elevates "exact match" signatures above "compatible" signatures.
The core problem is that an overload set is not a C++ object. You can name an overload set, but you cannot pass it around "natively".
Now, you can create a pseudo-overload set of a function like this:
#define RETURNS(...) \
noexcept(noexcept(__VA_ARGS__)) \
-> decltype(__VA_ARGS__) \
{ return __VA_ARGS__; }
#define OVERLOADS_OF(...) \
[](auto&&...args) \
RETURNS( __VA_ARGS__(decltype(args)(args)...) )
this creates a single C++ object that can do overload resolution on a function name.
Expanding the macros, we get:
[](auto&&...args)
noexcept(noexcept( baz(decltype(args)(args)...) ) )
-> decltype( baz(decltype(args)(args)...) )
{ return baz(decltype(args)(args)...); }
which is annoying to write. A simpler, only slightly less useful, version is here:
[](auto&&...args)->decltype(auto)
{ return baz(decltype(args)(args)...); }
we have a lambda that takes any number of arguments, then perfect forwards them to baz
.
Then:
class Bar {
std::function<void()> bazFn;
public:
Bar(std::function<void()> fun = OVERLOADS_OF(baz)) : bazFn(fun){}
};
works. We defer overload resolution into the lambda that we store in fun
, instead of passing fun
an overload set directly (which it cannot resolve).
There has been at least one proposal to define an operation in the C++ language that converts a function name into an overload set object. Until such a standard proposal is in the standard, the OVERLOADS_OF
macro is useful.
You could go a step further, and support cast-to-compatible-function-pointer.
struct baz_overloads {
template<class...Ts>
auto operator()(Ts&&...ts)const
RETURNS( baz(std::forward<Ts>(ts)...) );
template<class R, class...Args>
using fptr = R(*)(Args...);
//TODO: SFINAE-friendly support
template<class R, class...Ts>
operator fptr<R,Ts...>() const {
return [](Ts...ts)->R { return baz(std::forward<Ts>(ts)...); };
}
};
but that is starting to get obtuse.
Live example.
#define OVERLOADS_T(...) \
struct { \
template<class...Ts> \
auto operator()(Ts&&...ts)const \
RETURNS( __VA_ARGS__(std::forward<Ts>(ts)...) ); \
\
template<class R, class...Args> \
using fptr = R(*)(Args...); \
\
template<class R, class...Ts> \
operator fptr<R,Ts...>() const { \
return [](Ts...ts)->R { return __VA_ARGS__(std::forward<Ts>(ts)...); }; \
} \
}
The issue here is there is nothing telling the compiler how to do the function to pointer decay. If you have
void baz(int i) { }
void baz() { }
class Bar
{
void (*bazFn)();
public:
Bar(void(*fun)() = baz) : bazFn(fun){}
};
int main(int argc, char **argv)
{
Bar b;
return 0;
}
Then code would work since now the compiler knows which function you want as there is a concrete type you are assigning to.
When you use std::function
you call it's function object constructor which has the form
template< class F >
function( F f );
and since it is a template, it needs to deduce the type of the object that is passed. since baz
is an overloaded function there is no single type that can be deduced so template deduction fails and you get an error. You would have to use
Bar(std::function<void()> fun = (void(*)())baz) : bazFn(fun){}
to get force a single type and allow the deduction.