Date: Wed, 26 Feb 2025 21:38:56 -0500
I noticed that unlike when initializing function pointers, std::function
(and copyable_function, etc.) can't select the right overload of a function
when it is initialized. For example,
double add(double, double);
float add(float, float);
double(*fPtr)(double, double) = add; // Okay, selects correct overload
std::function<double(double, double)> func1 = add; // Error, cannot
determine correct overload
std::function<double(double, double)> func2 = static_cast<double(*)(double,
double)>add; // Ugly fix
will fail to compile without a cast.
This is because the only way to convert a Callable to std::function is with
the constructor taking a template parameter F&&, so if an overload set is
passed, the template argument cannot be deduced, and an error is produced
since there are no other viable constructors. Therefore it is necessary to
manually specify the overload by using the signature of the function again
with a cast.
However, I think it could be fixed by adding a constructor taking a
parameter of type R(*)(Args...) and a similar assignment operator to
std::function which could simply pass their arguments on to the regular
constructor. These would be preferred over the template constructor, and
the concrete types would allow the compiler to find the right overload.
They wouldn't create problems with non-function pointer types because the
template constructor would be a better match during constructor selection
due to a lack of implicit conversions.
This would make std::function (and copyable_function, etc.) more consistent
with ordinary function pointer types. You could argue that we can
explicitly specify the type with a cast and rely on CTAD in the case of
std::function, but this will not work for the modern replacements since
they seem to have intentionally left out deduction guides. While it makes
sense to keep the type of the function object explicit, there is no need to
be doubly explicit, so I think the function types would be better if they
could find the right overload automatically. This would also make it easier
for beginners to use the function types since they could avoid error
messages that might be mysterious to them.
A small wrinkle is that it would be necessary to have an additional
overload taking a noexcept function pointer since we don't want to lose the
type information by implicit conversion (for .target_type()), but with just
these two extra constructors, the correct overload would be found.
It could even be extended to pointers to member functions, but more
overloads would be required for the various combinations of possible member
function signatures (12 for all combinations of ref-qualifiers, const , and
noexcept). The type that the pointer to member belongs to would have to be
a deducible template parameter if some base class contains an overload set.
This entails some complexity, so it might be controversial, but I felt it
was worth examining as a logical extension of the simple case of ordinary
function pointers.
I tried implementing this for constructors of std::function [here](
https://godbolt.org/z/6hj5rT6PE). There is a lot of code for the
pointer-to-member functions, but the code for ordinary function pointers is
only a few lines. I think it shouldn't be too hard to write similar code
for the newer replacements of std::function too. What do you think of it?
Has anyone proposed or considered something like this before? Someone said
this could be considered a defect, but it seems more like a proposal to
me, especially with pointers to members. Also I'm new to this forum, and I
would appreciate any feedback!
- Jack
(and copyable_function, etc.) can't select the right overload of a function
when it is initialized. For example,
double add(double, double);
float add(float, float);
double(*fPtr)(double, double) = add; // Okay, selects correct overload
std::function<double(double, double)> func1 = add; // Error, cannot
determine correct overload
std::function<double(double, double)> func2 = static_cast<double(*)(double,
double)>add; // Ugly fix
will fail to compile without a cast.
This is because the only way to convert a Callable to std::function is with
the constructor taking a template parameter F&&, so if an overload set is
passed, the template argument cannot be deduced, and an error is produced
since there are no other viable constructors. Therefore it is necessary to
manually specify the overload by using the signature of the function again
with a cast.
However, I think it could be fixed by adding a constructor taking a
parameter of type R(*)(Args...) and a similar assignment operator to
std::function which could simply pass their arguments on to the regular
constructor. These would be preferred over the template constructor, and
the concrete types would allow the compiler to find the right overload.
They wouldn't create problems with non-function pointer types because the
template constructor would be a better match during constructor selection
due to a lack of implicit conversions.
This would make std::function (and copyable_function, etc.) more consistent
with ordinary function pointer types. You could argue that we can
explicitly specify the type with a cast and rely on CTAD in the case of
std::function, but this will not work for the modern replacements since
they seem to have intentionally left out deduction guides. While it makes
sense to keep the type of the function object explicit, there is no need to
be doubly explicit, so I think the function types would be better if they
could find the right overload automatically. This would also make it easier
for beginners to use the function types since they could avoid error
messages that might be mysterious to them.
A small wrinkle is that it would be necessary to have an additional
overload taking a noexcept function pointer since we don't want to lose the
type information by implicit conversion (for .target_type()), but with just
these two extra constructors, the correct overload would be found.
It could even be extended to pointers to member functions, but more
overloads would be required for the various combinations of possible member
function signatures (12 for all combinations of ref-qualifiers, const , and
noexcept). The type that the pointer to member belongs to would have to be
a deducible template parameter if some base class contains an overload set.
This entails some complexity, so it might be controversial, but I felt it
was worth examining as a logical extension of the simple case of ordinary
function pointers.
I tried implementing this for constructors of std::function [here](
https://godbolt.org/z/6hj5rT6PE). There is a lot of code for the
pointer-to-member functions, but the code for ordinary function pointers is
only a few lines. I think it shouldn't be too hard to write similar code
for the newer replacements of std::function too. What do you think of it?
Has anyone proposed or considered something like this before? Someone said
this could be considered a defect, but it seems more like a proposal to
me, especially with pointers to members. Also I'm new to this forum, and I
would appreciate any feedback!
- Jack
Received on 2025-02-27 02:39:12