On 2015–05–13, at 8:26 AM, Houe <mboyk...@gmail.com> wrote:Here is a proposal document for a smart function pointer.
On 2015–05–13, at 7:26 PM, Houe <mboyk...@gmail.com> wrote:Thanks for taking the time to examine it. Yes, equality comparison is the main feature this proposal improves on over std::function.
I do not know of a viable strategy to add equality into function and neither do the designers (if there was it would have it by now).
In my proposal I references the boost FAQ as to why std::function is unable to provide equality comparison.
Equality comparison is the key feature and opens up so many more possibilities than std::function has.
auto lam1 = [state=0]() mutable { return state++; };
std::function<int(void)> f1 = lam1; lam1();
std::function<int(void)> f2 = lam1;
return (f1 == f2); // true or false? f1 and f2 have different behaviors
auto lam1 = [state=0]() mutable { return state++; };
std::function<int(void)> f1 = lam1;
std::function<int(void)> f2 = f1;
assert(f1 == f2); // "obviously" true at this point, right?
f1();
assert(f1 == f2); // is it still true at this point? f1 and f2 have different behaviors
On 2015–05–14, at 2:03 PM, Arthur O'Dwyer <arthur....@gmail.com> wrote:
OTOH, if we ignore these "difficult" cases, then what you describe in the paper can be implemented as a (very thin wrapper around a) std::shared_ptr<std::function<SignatureType>>. Your make_fun is equivalent to make_shared and can be implemented in terms of it.
On 2015–05–14, at 2:27 PM, David Krauss <pot...@mac.com> wrote:The final result needs to be a std::function for polymorphism to work. You could put the thin wrapper inside a std::function, but then you’d be arriving at my solutions which need to access the target without its exact type, e.g. target<void>().
auto f1 = make_fun([]() { /*...*/ });
auto f2 = f1; //copy constructor - shares target with d1.
for (int i = 0; i < 1000; ++i) {
assert(f1 == f2); //true each time through the loop
if (rand() % 2 == 0) f1();
else f2();
}
auto lam1 = [state=0]() mutable { return state++; };
std::fun_ptr<int(void)> f1 = lam1; lam1();
std::fun_ptr<int(void)> f2 = lam1;
return (f1 == f2); // false, if fact calling lam1() or not does not change this
STL function objects are modeled after function pointers, so the convention in the STL is that function objects, too, are passed by value (i.e., copied) when passed to and from functions.
auto lam1 = [state=0]() mutable { return state++; };
std::fun_ptr<int(void)> f1 = lam1;
std::fun_ptr<int(void)> f2 = f1;
assert(f1 == f2); // true, f2 was just assigned f1 so it better be!
f1(); //operator() is const and this is not a salient operation
assert(f1 == f2); // true, and f1 and f2 do NOT have different behaviors (std::function does have different behaveior)
On Thu, May 14, 2015 at 7:23 AM, Michael Boyko <mboyk...@gmail.com> wrote:
> Arthur,
>
> Good examples that do need answers. fun_ptr answers these in a way
> consistent with function pointer like semantics.
You mean shared_ptr<function> semantics.
[...]
> In my paper I document that a smart function pointer is designed to have
> function pointer like semantics. Consider calling/invoking a regular old
> standalone function pointer: it does not change the pointer's value and so
> too calling operator() on fun_ptr should not change its value (and it
> doesn't). I'd also make the argument that since operator() is a const member
> function it should not change the types value (that is operator() is NOT a
> salient operation).
operator() is not always a const member function. To take a concrete example:
> auto lam1 = [state=0]() mutable { return state++; };
In the above example, the lambda type's operator() is not a const
member function,
and does modify the state of the lambda object.
> fun_ptr meets all three of these value type properties. Its hard to imagine
> how std::function could in general satisfy these 3 properties since it
> performs a deep copy of the contained function object.
[...but then later you say...]
> This is why we want fun_ptr to accept function objects by value. Its akin to
> assigning one function pointer to another. When a fun_ptr is created it
> copies the functor (and state if any)
Does fun_ptr make a deep copy, or not? From your proposal it's pretty
clear that you *do* think of make_fun(f1) as making a deep copy of f1,
in exactly the same way as make_shared<function<F>>(f1). However,
whenever anyone brings this up, you switch to saying "oh no, there are
no deep copies here."
> I will try to add some of these examples and explanations to my proposal - I
> don't think I am marketing this very well...
You're just not realizing that (1) shared_ptr and function already
exist, and that (2) combining them gives you exactly the semantics
you're looking for.
I've attached a copy of "event.h" with delegate_type replaced with
shared_ptr<function>, so you can see concretely what that looks like.
HTH,
–Arthur
I think you are missing that fun_ptr can handle standalone functions as well as member functions in addition to function objects. Consider this:auto f1 = make_fun(&standalone);auto f2 = make_fun(&standalone);assert(f1 == f2); // this is true for fun_ptr, but would not be true if implemented in terms of shared_ptr<function<F>>I think that addresses a lot of your comments but I do have a few other below...
agreed. When a fun_ptr is constructed with a functor it does make a separate copy of the lambda and then manages it and shares when fun_ptr is copied. No two fun_ptr's constructed from lambda's (even the same lambda) will ever be equal. However constructing two fun_ptrs from standalone functions will compare equal.
1) Performs equality testing, but only if both were filled with a function pointer or member function pointer. No effort to test equality is made for any other callable type, even if they are technically equality comparable.
There's no reason std::function couldn't be extended to handle #1. Boost doesn't like this solution, since they'd prefer that two boost::function objects that store different types to fail to compile if you test equality. But the standard committee won't a priori reject such a proposal for that reason.
Class Foo {
public:
Foo(int state) {/*...*/}
void bar() {/*...*/}
}
int state = 0;
Foo foo1(state);
Foo foo2(state);
fun_ptr<void()> fp1(&Foo::bar, &foo1);
fun_ptr<void()> fp2(&Foo::bar, &foo2);
compare = (fp1 == fp2);
auto foo = [=]() {/*...*/};
fun_ptr<void()> fp1(foo);
fun_ptr<void()> fp2(foo);
compare = (fp1 == fp2);
Herb Sutter a while back wrote a article called Generalizing Observer. Under this article is a section called "An Important Limitation of function". Sutter says at one point "This lack of comparison operations is one limitation of function, and it is significant." In fact he suggested equality comparison for std::function in this article. Comparison of callback functions is a common operation that is quite pervasive in code.
auto foo = []() {/*...*/}; //Notice something missing.
fun_ptr<void()> fp1(foo);
fun_ptr<void()> fp2(foo);
compare = (fp1 == fp2);
event.register(fp1);
event.unregister(fp2);
Yet, equality comparison between functors can only make sense if the two types are equal or if they have an operator== implementation between them.
That instantly rules out lambdas, since you can't make operator== implementations for them.
On 14 May 2015 at 17:39, Nicol Bolas <jmck...@gmail.com> wrote:
Yet, equality comparison between functors can only make sense if the two types are equal or if they have an operator== implementation between them.I would say "equality comparison between functors can only make sense if the two types are equal and if they have an operator== implementation between them."
That instantly rules out lambdas, since you can't make operator== implementations for them.That may change, as the general direction of N4475 was received favorably.
Another question: should a captureless lambda compare equal to its function pointer equivalent; i.e.,:auto l = []{ /* ... */};auto f = +l;are f and l equal?
On 2015–05–15, at 7:08 AM, Nevin Liber <ne...@eviloverlord.com> wrote:I would say "equality comparison between functors can only make sense if the two types are equal and if they have an operator== implementation between them."
auto l = []{ /* ... */};auto f = +l;are f and l equal?
On Thursday, May 14, 2015 at 5:22:58 PM UTC-4, Michael Boyko wrote:Herb Sutter a while back wrote a article called Generalizing Observer. Under this article is a section called "An Important Limitation of function". Sutter says at one point "This lack of comparison operations is one limitation of function, and it is significant." In fact he suggested equality comparison for std::function in this article. Comparison of callback functions is a common operation that is quite pervasive in code.
If it's such a "common operation" that is "quite pervasive", then surely you can do better than repeat the same example. Or at the very least, you could find an example where the lack of such comparison isn't easily handled. And even moreso, handled in a way that ultimately makes for a better C++ interface.
auto l = []{ /* ... */};auto f = +l;are f and l equal?
On 2015–05–15, at 7:08 AM, Nevin Liber <ne...@eviloverlord.com> wrote:I would say "equality comparison between functors can only make sense if the two types are equal and if they have an operator== implementation between them."Sounds like a job for a concept (or presently, a user-defined type trait).
auto l = []{ /* ... */};auto f = +l;are f and l equal?The problem is the implicit conversion. One solution is to add two deleted operator== overloads in addition to the default one, to prevent such comparisons.
On 2015–05–15, at 11:13 AM, Nevin Liber <ne...@eviloverlord.com> wrote:I don't understand what concepts, being a compile time mechanism, has to do with either defining how a run time comparison should behave or how one implements heterogeneous comparison between two type erased instances. Could you elaborate?
auto l = []{ /* ... */};auto f = +l;are f and l equal?The problem is the implicit conversion. One solution is to add two deleted operator== overloads in addition to the default one, to prevent such comparisons.Again, I don't understand why this is problematic, other than with trying to add this to something like std::function. Could you elaborate?
I think discussing if two functors is equal or not is interesting but the real question is what does it mean for a function pointer to be equal to another function pointer. I don't think the state of the functor being pointed to is all that important. The only property (or salient attribute) that matters is does the pointer point to the same object and function or not. It is a shallow comparison not a deep one.
On Thursday, May 14, 2015 at 5:39:40 PM UTC-5, Nicol Bolas wrote:On Thursday, May 14, 2015 at 5:22:58 PM UTC-4, Michael Boyko wrote:Herb Sutter a while back wrote a article called Generalizing Observer. Under this article is a section called "An Important Limitation of function". Sutter says at one point "This lack of comparison operations is one limitation of function, and it is significant." In fact he suggested equality comparison for std::function in this article. Comparison of callback functions is a common operation that is quite pervasive in code.
If it's such a "common operation" that is "quite pervasive", then surely you can do better than repeat the same example. Or at the very least, you could find an example where the lack of such comparison isn't easily handled. And even moreso, handled in a way that ultimately makes for a better C++ interface.I can offer my personal use of this library. In the last two companies I have worked for we used this library (minus functor targets) with good results. The first project was on the order of 400k lines of code and this library was used extensively throughout. My current project is also making use of this library with equally good results. The primary use is callbacks and searching for matching callbacks in lists and vectors.
Secondly, there is a very popular article I referenced in my paper called Member Function Pointers and the Fastest Possible C++ Delegates. This implementation has basically the same exact semantics (minus the functor target as my proposal) and is a very popular article with over 1.6 million visits and over 20k downloads. Popularity is hurt because that implementation uses non standard C++ to improve performance.
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposal...@isocpp.org.
To post to this group, send email to std-pr...@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
On 2015–05–15, at 3:48 PM, Brent Friedman <fourt...@gmail.com> wrote:In the UI, it would be nice to organize the menu so that commands applicable to all selected objects are perhaps sorted to the top, and commands applicable to only a subset of objects perhaps appear in a submenu. It is at this point where comparing two std functions becomes very useful. We need that equality comparison to detect this overlap.
On Thursday, May 14, 2015 at 11:13:51 PM UTC-4, mash.bo...@gmail.com wrote:I think discussing if two functors is equal or not is interesting but the real question is what does it mean for a function pointer to be equal to another function pointer. I don't think the state of the functor being pointed to is all that important. The only property (or salient attribute) that matters is does the pointer point to the same object and function or not. It is a shallow comparison not a deep one.
And I could find someone who believes quite differently. That what matters for function equality is that passing the same parameters will produce the same results and same side-effects, for all possible sets of parameters (given other invariants like global state and the like). In this case, the state of functors is very relevant. What is conceptually calling the same code, but do different things based on internal state. But if the internal state compares equal, then they are the same functor.
Again, I go back to my lambda vs. function pointer version. People would be very surprised that the version with naked functions works but the lambdas don't.
I'm of the opinion that there is no single reasonable standard that works for everyone, or even most people. Different people simply have different equality testing needs when it comes to functors. And if you try to define one standard, what you do is break the class for people who need a different standard.
Another question: should a captureless lambda compare equal to its function pointer equivalent; i.e.,:auto l = []{ /* ... */};auto f = +l;are f and l equal?
Ugh. How many other gotcha cases are there with regard to function equality? It's sounding better and better to just design interfaces to avoid the whole issue...
class functor {
public:
void operator()() {...}
};
functor f;
auto fp1 = fun_ptr<void()>(&functor::operator(), &f);
auto fp2 = fun_ptr<void()>(f);
bool compare = (fp1 == fp2); //what should the result be?
On Thursday, May 14, 2015 at 10:41:29 PM UTC-4, mash.bo...@gmail.com wrote:On Thursday, May 14, 2015 at 5:39:40 PM UTC-5, Nicol Bolas wrote:On Thursday, May 14, 2015 at 5:22:58 PM UTC-4, Michael Boyko wrote:Herb Sutter a while back wrote a article called Generalizing Observer. Under this article is a section called "An Important Limitation of function". Sutter says at one point "This lack of comparison operations is one limitation of function, and it is significant." In fact he suggested equality comparison for std::function in this article. Comparison of callback functions is a common operation that is quite pervasive in code.
If it's such a "common operation" that is "quite pervasive", then surely you can do better than repeat the same example. Or at the very least, you could find an example where the lack of such comparison isn't easily handled. And even moreso, handled in a way that ultimately makes for a better C++ interface.I can offer my personal use of this library. In the last two companies I have worked for we used this library (minus functor targets) with good results. The first project was on the order of 400k lines of code and this library was used extensively throughout. My current project is also making use of this library with equally good results. The primary use is callbacks and searching for matching callbacks in lists and vectors.
You seem to have missed my question. Yes, by using code that allows you to compare function pointers, you can have vectors and lists of function pointers and find matches in them. That's not in doubt.
The question is why you need to do that? What is your use case? Not merely that you used the functionality, but what you were using it to accomplish.
You created an ordered sequence of callable objects. Then, at some point in time, you searched that sequence of callable objects for a specific one. What were you doing that you needed to do that? What was the problem that prompted you to search through the ordered list of callables for a specific callable?
And if it's yet another event/delegate/signal implementation, then you've missed my point entirely. Because there's a better, more C++-friendly way to handle that sort of thing.
Secondly, there is a very popular article I referenced in my paper called Member Function Pointers and the Fastest Possible C++ Delegates. This implementation has basically the same exact semantics (minus the functor target as my proposal) and is a very popular article with over 1.6 million visits and over 20k downloads. Popularity is hurt because that implementation uses non standard C++ to improve performance.
I'm not exactly sure how that's relevant in this case. You'd be talking about a callback/event/observable that you can't shove a lambda into. As far as I'm concerned, that's a deal-breaker. Outside of certain very specific performance cases, being unable to use a lambda for a callback is a non-starter.
That's not to say that such classes shouldn't exist. But I see no reason for the standard library to have or promote such functions. And certainly, if the standard library were to create some kind of delegate/event/signal object, it should use a polymorphic function object that can handle lambdas. Stateful lambdas.
I see a pointer-only proposal as someone wanting to standardize a fixed-length string, or a vector that does small-buffer optimization. These are all legitimate optimizations. But they're also very special-case; we need to cover the generally useful cases first.
On Friday, May 15, 2015 at 12:15:40 AM UTC-5, Nicol Bolas wrote:On Thursday, May 14, 2015 at 10:41:29 PM UTC-4, mash.bo...@gmail.com wrote:I can offer my personal use of this library. In the last two companies I have worked for we used this library (minus functor targets) with good results. The first project was on the order of 400k lines of code and this library was used extensively throughout. My current project is also making use of this library with equally good results. The primary use is callbacks and searching for matching callbacks in lists and vectors.
You seem to have missed my question. Yes, by using code that allows you to compare function pointers, you can have vectors and lists of function pointers and find matches in them. That's not in doubt.
The question is why you need to do that? What is your use case? Not merely that you used the functionality, but what you were using it to accomplish.
You created an ordered sequence of callable objects. Then, at some point in time, you searched that sequence of callable objects for a specific one. What were you doing that you needed to do that? What was the problem that prompted you to search through the ordered list of callables for a specific callable?
And if it's yet another event/delegate/signal implementation, then you've missed my point entirely. Because there's a better, more C++-friendly way to handle that sort of thing.Basically it is searching containers of callbacks and adding/removing from that list.
Yes, we also have RAII scoped attach objects that are nice and used, but sometimes its not the right tool for the job (sometime a lock_guard is not the right tool and calling lock() and unlock() directly is).
I'm sure its possible to do what we are doing without equality comparison, but I'm not going to buy the argument of that being proof it isn't a useful feature.
Maybe this type would not be useful for you - I don't know what types of code you write. There is certainly stuff in the standard library I've never used or found useful yet. But it is relevant because these things have been found to be useful by quite a few people. Dozens of these things have been created and continue to be. Why do these things continue to be created when std::function already exists?
Although I don't feel strongly in favor of this proposal, I can offer at least an example use-case where comparing std::functions would be very useful. This example has not been implemented so is speculative and theoretical. However it does relate directly to some work that I intend to do.
<snip>
It seems to me that you already conceptually have a GUID
Players can detach themselves by taking an antidote or some other player casting a reverse spell.
--
Players can detach themselves by taking an antidote or some other player casting a reverse spell.I do have code with a flavor similar to this. I have a complex object (a 3d viewport) which can have callbacks attached to it for various events. Many places can register or unregister these callbacks. In that system, registering a callback returns a handle (iterator). That handle is used to unregister. I've found this to work perfectly well in my case. Your case is slightly different in that the antidote probably applies to a class of spells rather than applying to a specific instance of a spell. It seems like using an id for comparing functions is most correct here. As soon as you have a spell which can apply random effects (selects from one of several std::functions) the function equality technique could break down or become onerous. It also seems wasteful and architecturally onerous to require construction of the std::function just so you can do equality comparison to unregister.
On Friday, May 15, 2015 at 9:17:12 AM UTC-5, Nicol Bolas wrote:On Friday, May 15, 2015 at 9:37:53 AM UTC-4, Michael Boyko wrote:On Friday, May 15, 2015 at 12:15:40 AM UTC-5, Nicol Bolas wrote:On Thursday, May 14, 2015 at 10:41:29 PM UTC-4, mash.bo...@gmail.com wrote:I can offer my personal use of this library. In the last two companies I have worked for we used this library (minus functor targets) with good results. The first project was on the order of 400k lines of code and this library was used extensively throughout. My current project is also making use of this library with equally good results. The primary use is callbacks and searching for matching callbacks in lists and vectors.
You seem to have missed my question. Yes, by using code that allows you to compare function pointers, you can have vectors and lists of function pointers and find matches in them. That's not in doubt.
The question is why you need to do that? What is your use case? Not merely that you used the functionality, but what you were using it to accomplish.
You created an ordered sequence of callable objects. Then, at some point in time, you searched that sequence of callable objects for a specific one. What were you doing that you needed to do that? What was the problem that prompted you to search through the ordered list of callables for a specific callable?
And if it's yet another event/delegate/signal implementation, then you've missed my point entirely. Because there's a better, more C++-friendly way to handle that sort of thing.Basically it is searching containers of callbacks and adding/removing from that list.
So it's just this event/delegate/signal thing.
Yes, we also have RAII scoped attach objects that are nice and used, but sometimes its not the right tool for the job (sometime a lock_guard is not the right tool and calling lock() and unlock() directly is).
I don't know why you keep talking about "RAII scoped attach objects". I've brought up Boost.Signal as an alternative event/delegate/signal system several times. While it does use an attachment object for you to manipulate functor bindings, these connection objects are not "RAII scoped".
The use of connection objects does not require that they're RAII scoped. That certainly should be an option, but just like with mutexes, they're not required.
I'm sure its possible to do what we are doing without equality comparison, but I'm not going to buy the argument of that being proof it isn't a useful feature.
That makes no sense. If there's a different way to do what you need, which in no way negatively impacts the existing users, as well as allows them more freedom in how they use that functionality... we have a term for that. It's "a better way."
We should not be encouraging a view of C++ where function/member pointers are given greater primacy than functors/lambdas.Maybe this type would not be useful for you - I don't know what types of code you write. There is certainly stuff in the standard library I've never used or found useful yet. But it is relevant because these things have been found to be useful by quite a few people. Dozens of these things have been created and continue to be. Why do these things continue to be created when std::function already exists?
The same reasons people give for making new string types: greater control, performance benefits, herd mentality, inertia, legacy codebases, etc. There are many things people back-door in the standard library, for both good reasons and bad. Why is yours so special?
Also: what people have found useful is not polymorphic function pointers. It's events/delegates/signals. And I'd bet they'd find them more useful if they could shove a lambda into them.Consider this example:There is some event E. Suppose we have two objects A and B. Object A can have a dependency on B but B cannot have a dependency on A. Object A might attach a member function of object B to E. Now a few different scenarios might be allowed by program flow:1. Maybe Object A could go out of scope and B still might need the capability to detach itself from E.
2. Maybe A would do the detach or Maybe B would do the detach whoever has reason to do it first.
3. Maybe another Object C comes into existence and wants to detach B from E.
Where should this connection objects live? Maybe some global object?
4. Maybe the above scenario is multiplied by few thousand with various interactions. A global connection object for every conceivable connection? Maybe a global matrix for every possible connection?This scenario could mimic a RPG game. Event E is some spell. Object A and B are players. Spells are cast on other players by attaching to the event/spell. Players can detach themselves by taking an antidote or some other player casting a reverse spell.
I didn't say the event was a member of A and I'll disagree with you and say object B has every right to attach A to some event. Its a valid design.
[ptr = weak_ptr<A>{smart_ptr}](...)
{
shared_ptr<A> p = ptr.lock();
if(p) { return p->MemberFunction(...);}
return {};
}
Ugh. How many other gotcha cases are there with regard to function equality?
On 2015–05–16, at 5:35 AM, Nevin Liber <ne...@eviloverlord.com> wrote:
What will a type trait / concept for equality comparable return for T?
… And if No, it’s a hard failure for Functor.
- Depends on T
What will a type trait / concept for equality comparable return for Functor<T>?
- Yes
Feel free to try it with, say, has_equal_to, and then think about the implications…
On 2015–05–16, at 7:28 AM, David Krauss <pot...@mac.com> wrote:The same failure mode will be exposed by implicit operator==, any time it sees a Functor<T> member.
… I get the strong sense that EWG isn’t interested in every user’s potential internal template errors. New features include breaking changes, and hardly anything won’t break a sufficiently brittle template.
On 2015–05–16, at 12:33 PM, Nevin Liber <ne...@eviloverlord.com> wrote:I guess we'll find out for sure when you propose this "improvement" to std::function…
On 2015–05–16, at 12:49 PM, David Krauss <pot...@mac.com> wrote:Anyone who wants to pick up the ball is free to run with it.
On 2015–05–16, at 12:49 PM, David Krauss <pot...@mac.com> wrote:Anyone who wants to pick up the ball is free to run with it.
Actually, the implementation I described could be generalized into a facility for registering type-based metadata for inclusion into erasures. This could be used to implement things like target_type and comparison.
<snip>
On 2015–05–16, at 3:14 PM, Nicol Bolas <jmck...@gmail.com> wrote:The part about associating a type with a compile-time value. Rather than having to put code explicitly within each class like that, some form of compile-time reflection should be able to associate compile-time "named" values with classes, so that they can be compile-time retrieved later.
On Thursday, May 14, 2015 at 11:13:51 PM UTC-4, mash.bo...@gmail.com wrote:I think discussing if two functors is equal or not is interesting but the real question is what does it mean for a function pointer to be equal to another function pointer. I don't think the state of the functor being pointed to is all that important. The only property (or salient attribute) that matters is does the pointer point to the same object and function or not. It is a shallow comparison not a deep one.
And I could find someone who believes quite differently. That what matters for function equality is that passing the same parameters will produce the same results and same side-effects, for all possible sets of parameters (given other invariants like global state and the like). In this case, the state of functors is very relevant. What is conceptually calling the same code, but do different things based on internal state. But if the internal state compares equal, then they are the same functor.
Here is a strategy that I believe could compare all function types with the one constraint that mutating functors and lambdas are not allowed (reasonable in light of N4159 and N4348) and all user defined functors would require an operator==.Operator==1. If target types are different return false2. else if targets are standalone funtions return true if function pointers match3. else if targets are member functions return true if member function pointers and object instance pointers match4. else if targets are functor/lambdaA. If functor/lambda types are different return falseB. else if operator== is defined return its resultC. else return true
On 2015–05–17, at 1:29 PM, Ville Voutilainen <ville.vo...@gmail.com> wrote:That paper hasn't been discussed yet. As far as I understand, it will
be revisited
in Kona. I can't make any real predictions on it, but I can
for-full-disclosure say
that there is heavy opposition to that proposal due to compatibility issues.
And in case that wasn't clear, I will be raising such opposition.
On Sunday, May 17, 2015 at 12:34:37 AM UTC-4, mash.bo...@gmail.com wrote:Here is a strategy that I believe could compare all function types with the one constraint that mutating functors and lambdas are not allowed (reasonable in light of N4159 and N4348) and all user defined functors would require an operator==.Operator==1. If target types are different return false2. else if targets are standalone funtions return true if function pointers match3. else if targets are member functions return true if member function pointers and object instance pointers match4. else if targets are functor/lambdaA. If functor/lambda types are different return falseB. else if operator== is defined return its resultC. else return true
Assuming N4348 goes through (and it's not clear to me where the committee is on that one. Anybody with actual knowledge care to chime in?), this change to std::function seems reasonable. But it has created a problem:
The ability to attach "member function pointers and object instance pointers".
I understand the argument, especially now that you mostly have operator== ironed out. You want to be able to test to see if a particular member function and its instance data are the same, so that you can do whatever it is you do with that knowledge.
The problem is ownership. Or rather, the lack of ownership.
Those "object instance pointers" have got to be owned by someone. Are you going to allow these pointers to be smart pointers? What about comparing different pointer types, like if a pointer was registered with a std::shared_ptr, but is being compared with a std::observer_ptr or a naked pointer? Logically, the equality test shouldn't care, but your test doesn't take this into account.
And if you deny ownership at all, if you only allow naked pointers for such bindings, then... well, people are still going to want to give std::function ownership. So they'll be forced to manage memory, most likely by using a wrapper lambda. At which point, you've basically lost all of the equality testing potential here. So they'd have to implement some kind of wrapper non-lambda type.
And then that wrapper type would have to answer all the questions above. And do so in a way that erases the actual type of the member pointer and instance (smart) pointer from the interface, so that the wrapper type will be the same for all types.
It's just a big can of worms.
Lastly, you still never explained what you need this equality testing feature for. You want to make non-trivial changes to std::function, but you only have one use case. And even that's not so much a "can't do this without the feature" as "well, I can still do this without the feature, but I would rather not because... oh look, a bunny!"
On 2015–05–17, at 11:23 PM, mash.bo...@gmail.com wrote:void fn(int a) { ... }std::function<void(double)> fp1 = &fn; //really?That does not seem like a strongly typed universal function pointer.
On Sunday, May 17, 2015 at 12:14:37 AM UTC-5, Nicol Bolas wrote:On Sunday, May 17, 2015 at 12:34:37 AM UTC-4, mash.bo...@gmail.com wrote:Here is a strategy that I believe could compare all function types with the one constraint that mutating functors and lambdas are not allowed (reasonable in light of N4159 and N4348) and all user defined functors would require an operator==.Operator==1. If target types are different return false2. else if targets are standalone funtions return true if function pointers match3. else if targets are member functions return true if member function pointers and object instance pointers match4. else if targets are functor/lambdaA. If functor/lambda types are different return falseB. else if operator== is defined return its resultC. else return true
Assuming N4348 goes through (and it's not clear to me where the committee is on that one. Anybody with actual knowledge care to chime in?), this change to std::function seems reasonable. But it has created a problem:
The ability to attach "member function pointers and object instance pointers".
I understand the argument, especially now that you mostly have operator== ironed out. You want to be able to test to see if a particular member function and its instance data are the same, so that you can do whatever it is you do with that knowledge.
The problem is ownership. Or rather, the lack of ownership.
Those "object instance pointers" have got to be owned by someone. Are you going to allow these pointers to be smart pointers? What about comparing different pointer types, like if a pointer was registered with a std::shared_ptr, but is being compared with a std::observer_ptr or a naked pointer? Logically, the equality test shouldn't care, but your test doesn't take this into account.
And if you deny ownership at all, if you only allow naked pointers for such bindings, then... well, people are still going to want to give std::function ownership. So they'll be forced to manage memory, most likely by using a wrapper lambda. At which point, you've basically lost all of the equality testing potential here. So they'd have to implement some kind of wrapper non-lambda type.
And then that wrapper type would have to answer all the questions above. And do so in a way that erases the actual type of the member pointer and instance (smart) pointer from the interface, so that the wrapper type will be the same for all types.
It's just a big can of worms.I don't want to go there - no object ownership. Maybe if someone wanted to traffic in ownership a class that is implemented in terms of this type would be an option instead of wrappers used by this type.
Lastly, you still never explained what you need this equality testing feature for. You want to make non-trivial changes to std::function, but you only have one use case. And even that's not so much a "can't do this without the feature" as "well, I can still do this without the feature, but I would rather not because... oh look, a bunny!"I already pointed out a couple good reasons:
template<typename Sig>
class event
{
public:
void connect(Sig *func_ptr);
template<typename T>
void connect(... *mem_func_ptr, T *t_ptr);
template<ConstCallableAndComparable Functor>
void connect(Functor *functor);
void disconnect(Sig *func_ptr);
template<typename T>
void disconnect(... *mem_func_ptr, T *t_ptr);
template<ConstCallableAndComparable Functor>
void disconnect(Functor *functor);
};
1. When where an attach and where the matching detach are in different scopes then a shared or copied connection object is required - propagating objects around and maybe adding additional physical dependencies.
2. there is no way to know by examining a connection what attach the connection object refers to - if you want to know that information you would have to associate the connection with some additional data.
3. function compares are more flexible. function compares can easily be used to create a connection like object for the case where that is better or it can simply perform on demand detaches without having to manage connection objects when that is better.
struct Functor
{
void operator()() {++state;}
int state;
};
auto fn = new Functor;
event->connect(&Functor::operator(), &fn);
event->connect(Functor{});
If you can't come up with some other reason, some other real use-case for function comparison besides events/delegates/signals, then just propose an event system like the above.
void execute_action(fun_ptr<void()> action) {
static fun_ptr<void()> last_executed = nullptr;
if (action != last_executed) {
action();
last_executed = std::move(action);
}
}
3. function compares are more flexible. function compares can easily be used to create a connection like object for the case where that is better or it can simply perform on demand detaches without having to manage connection objects when that is better.
Yes, you could have one or the other. So?
It would be a very dysfunctional codebase that attempts to use both. Especially for the same event/delegate/signal objects; that's just begging for disaster (then again, you seem perfectly fine with storing non-owning pointers in events long-term, so begging for disaster is really par for the course).
Disconnecting Equivalent Slots (Intermediate)
One can disconnect slots that are equivalent to a given function object using a form of the
signal::disconnect
method, so long as the type of the function object has an accessible==
operator. For instance:void foo() { std::cout << "foo"; } void bar() { std::cout << "bar\n"; }
boost::signals2::signal
<void ()> sig;sig.connect(&foo); sig.connect(&bar); sig(); // disconnects foo, but not bar sig.disconnect(&foo); sig();
However... what is conceptually wrong with registering an event that has modifiable internal state? After all, if I registered a member pointer+instance, you don't require that the instance pointer be `const` or that the member pointer be callable via a `const` instance. If a member pointer can modify its instance data, then why can't a functor?
That is, it's legal to do this:
struct Functor
{
void operator()() {++state;}
int state;
};
auto fn = new Functor;
event->connect(&Functor::operator(), &fn);
But not this, with the exact same object type:
event->connect(Functor{});
On Sunday, May 17, 2015 at 12:36:19 PM UTC-5, Nicol Bolas wrote:
If you can't come up with some other reason, some other real use-case for function comparison besides events/delegates/signals, then just propose an event system like the above.Comparing function pointer has been around since the beginnings of c and its used in ways I can't even imagine. If we did a search for function pointer comparison in a large code bases I have no doubt I would find many uses I can't possibly imagine.
Here is a reasonable programming problem for you to write...
Write a funciton called execute_action that takes an arbitrary action with a signature of returning void and taking no params (i.e. void() ). execute_action shall execute the action so long as the action is different from the previously executed action. That is the same action is not allowed to be executed back to back. Here is how i would write execute_action:
void execute_action(fun_ptr<void()> action) {
static fun_ptr<void()> last_executed = nullptr;
if (action != last_executed) {
action();
last_executed = std::move(action);
}
}
Want to implement this with std::function?
On Sunday, May 17, 2015 at 12:36:19 PM UTC-5, Nicol Bolas wrote:
However... what is conceptually wrong with registering an event that has modifiable internal state? After all, if I registered a member pointer+instance, you don't require that the instance pointer be `const` or that the member pointer be callable via a `const` instance. If a member pointer can modify its instance data, then why can't a functor?
That is, it's legal to do this:
struct Functor
{
void operator()() {++state;}
int state;
};
auto fn = new Functor;
event->connect(&Functor::operator(), &fn);
But not this, with the exact same object type:
event->connect(Functor{});First since the operator() function is not const so it wouldn't be legal.
And second I have thought and even pointed out in this thread the ambiguity between those 2 connects.
Boy you are being difficult :) Write a function to remove duplicated actions from a vector (std::vector<std::function<void()>>) and then execute them. You don't have to worry about ownership.
We could come up with a dozen container operations that equality is needed for (append unique action, find, count, find_adjacent, is_permutation, or some user defined ones). But I guess all container operations that require equality check are too arbitrary or low level - only for function objects but for other types its ok... got it.
And the boost signal library supporting direct attach/detach for types that do support equality - lets hope they get rid of that feature soon so all those bugs its causing can be fixed.
I'm asking you to provide evidence that there are situations where programmers absolutely need function equality. So answers to that question cannot be of the form, "Assume we need function equality..."
You need to show not merely where programmers have used it in the past, but undeniably need it. You need a problem statement that is reasonable (ie: not forced and contrived, like "no double action"), and where all other solutions are less effective, less clean, lose some important aspect, or otherwise represent a compromised solution.
On Tuesday, May 19, 2015 at 12:04:21 AM UTC-5, Nicol Bolas wrote:I'm asking you to provide evidence that there are situations where programmers absolutely need function equality. So answers to that question cannot be of the form, "Assume we need function equality..."
You need to show not merely where programmers have used it in the past, but undeniably need it. You need a problem statement that is reasonable (ie: not forced and contrived, like "no double action"), and where all other solutions are less effective, less clean, lose some important aspect, or otherwise represent a compromised solution.I hope "absolutely need" and "undeniably need" are not the judgment standards the committee uses for a new feature.
I'd have a pretty rough go of things. I'm hoping its more along the lines being useful and maybe fills in missing features to make the language more orthogonal and uniform.
I have updated my proposal and code as discussed in this thread. I published the proposal and code on code project for anyone to review. The code has been tested and bench marked (which can be seen on code project).
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposal...@isocpp.org.
To post to this group, send email to std-pr...@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
I am not sure what the definition of "value" is for you, or said otherwise, what are the salient attributes of 'fun_ptr'. In some places it seems like you want a strict: 'a == b => a() == b()', or rather that 'a == b' implies that evaluating either 'a()' or 'b()' yields the same result (as two consecutive evaluations of the same function could generate different results anyway ('int f() { static int counter; return ++counter; }').
What is it really that you care about with equality?
assert(a == b);
if (is_odd(rand())) a();
else b();
if (is_odd(rand())) a();
else b();
For example these two 'fun_ptr' will compare (according to the wording, have not tested the implementation):
struct base { void f(); };
struct derived : base {};
void (derived::*mptr)() = &derived::base;
derived d;
auto a = make_fun(mptr, &d);
auto b = make_fun(&derived::base, &d);
// a != b ?!?!? -- the type of holding the member in the pointer-to-member should not matter
On the other direction, there are cases where equality would also be broken on the other direction, the library claiming two 'fun_ptr' being "equal" when they are not. The requirement seems to come from attempting to consider 'fun_ptr's created from two lambda instances created by the same lambda expression as the same, but the type of the lambda is not sufficient to determine "equivalency":
auto c_str_getter(std::string const & s) { return [&s]() { return s.c_str(); }; }
std::string h("Hello"), w("world");
auto a = make_fun(c_str_getter(h));
auto b = make_fun(c_str_getter(w));
This case is semantically equivalent to:
auto a = make_fun(&std::string::c_str, h);
auto b = make_fun(&std::string::c_str, w);
But in the first case the library (again judging by the documentation) would claim 'a == b' in the first case but 'a != b' in the second case.
Beyond that case, the assumption that for a non-comparable type all instances are the same is completely bogus:
struct S { void f(); void g(); };
auto a = make_fun(std::mem_fn(&S::f));
auto b = make_fun(std::mem_fn(&S::g));
auto c_str_getter(std::string const & s) { return [&s]() { return s.c_str(); }; }
std::string h("Hello"), w("world");
auto a = make_fun(c_str_getter(h));
auto b = make_fun(c_str_getter(w));
This case is semantically equivalent to:
auto a = make_fun(&std::string::c_str, h);
auto b = make_fun(&std::string::c_str, w);
With respect to this:auto c_str_getter(std::string const & s) { return [&s]() { return s.c_str(); }; }
std::string h("Hello"), w("world");
auto a = make_fun(c_str_getter(h));
auto b = make_fun(c_str_getter(w));
This case is semantically equivalent to:
auto a = make_fun(&std::string::c_str, h);
auto b = make_fun(&std::string::c_str, w);... we would be in the territory of «functionally equivalent but not equivalent» statements, probably; informally same results when used, but expressed differently.
I don't see this proposal as a semantic change as much as an additional tool for such things as observer rejecting duplicates. Am I reading your intentions right, Michael?