C++ Logo

std-proposals

Advanced search

Re: [std-proposals] Initializing std::function with overload sets

From: Jarrad Waterloo <descender76_at_[hidden]>
Date: Wed, 26 Feb 2025 22:40:46 -0500
current example for known signatures

std::function<double(double, double)> func1{*nontype<*add*>*};
VS
std::function<double(double, double)> func2 = static_cast<double(*)(double,
double)>add; // Ugly fix

proposal
https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2023/p0792r14.html

implementation
https://github.com/zhihaoy/nontype_functional/tree/p0792r13
https://github.com/zhihaoy/nontype_functional/blob/p0792r13/tests/function_ref/test_nontype.cpp
https://github.com/zhihaoy/nontype_functional/blob/p0792r13/examples/listing_1.cpp
https://github.com/zhihaoy/nontype_functional/blob/p0792r13/examples/listing_2.cpp
https://github.com/zhihaoy/nontype_functional/blob/p0792r13/examples/listing_3.cpp

*rationale of the nontype constructors*
*https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2022/p2472r3.html
<https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2022/p2472r3.html>*

Personally I would use the nontype constructors by default especially for
all existing known signatures, it's safer, more efficient and easier to
use.

The nontype constructors are for the following scenarios.
free functions with type erased state
free functions without type erased state
member functions with type erased state
member functions without type erased state

You can also think of it as a runtime UFC, universal function call, or
dynamic trait that only supports one function.

If you want to bind last instead of the first parameter, there is a
seperate bind first/last proposal but that proposal might create a
temporary instance that the nontype seeks to avoid.



On Wed, Feb 26, 2025 at 9:39 PM Jack O'Donohue via Std-Proposals <
std-proposals_at_[hidden]> wrote:

> 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
> --
> Std-Proposals mailing list
> Std-Proposals_at_[hidden]
> https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals
>

Received on 2025-02-27 03:40:59