smart function pointer proposal

481 views
Skip to first unread message

Houe

unread,
May 12, 2015, 8:26:49 PM5/12/15
to std-pr...@isocpp.org
Here is a proposal document for a smart function pointer. It is purely a library proposal - no additional language features are needed. I believe I've got the correct syntax and semantics for the type but looking for feedback. I've included 2 different implementations for the smart pointer (fun_ptr2 and fun_ptr3). Version 2 is easier to understand and follow while version3 is probably 10x performance. I've also included example and test code. I created an event<> type based on the smart function pointer that has nice syntax and semantics as well to show a very good use for the type. Just posting it here to gauge if the community is interested in such a library.
smart_function_pointer.zip

David Krauss

unread,
May 12, 2015, 10:48:30 PM5/12/15
to std-pr...@isocpp.org
On 2015–05–13, at 8:26 AM, Houe <mboyk...@gmail.com> wrote:

Here is a proposal document for a smart function pointer.

Function wrapper types and memory management are a bit complicated, so it’s important to leverage existing functionality. I think std::function (and any successors) should completely handle type erasure, std::shared_ptr should implement reference counting, and std::reference_wrapper should indicate functor observer semantics. These three components can already be combined:

template< typename t >
class shared_reference
    : public std::reference_wrapper< t > {
    std::shared_ptr< t > ptr;
public:
    /* implicit */ shared_reference( std::shared_ptr< t > in_ptr )
        : std::reference_wrapper< t >( * in_ptr )
        , ptr( std::move( in_ptr ) )
        {}
};

// Demo:
struct heavyweight {
    std::map< std::string, payload > big_state;

    foo operator () ( bar );
    fizz operator () ( buzz );
};

shared_reference< heavyweight > on_heap = std::make_shared< heavyweight >();
std::function< foo( bar ) > = on_heap;
std::function< fizz( buzz ) > = on_heap;

Composing a reference_wrapper with a shared_ptr does lead to duplication of state, which is likely to tip std::function into the heap allocation regime. You could implement the reference_wrapper interface yourself based using the shared_ptr, and if the use-case is common enough, perhaps the standard library should provide that. If the operator-dot proposal brings us more transparent reference classes, there will be similar use-cases outside functions too, and such a standard template becomes more likely.

Unfortunately, std::bind respects only bona-fide std::reference_wrapper objects, not derived ones. This is easily fixable, though. It’s possible to roughly detect inheritance from a template specialization by inspecting the injected-class-name, e.g. the standard could say “if typename TiD::reference_wrapper is reference_wrapper<T>, the argument is tid.get() and its type Vi is T&.” Phrasing it exactly thus would allow a custom wrapper with no inheritance, but implementing a member typedef and get.

Houe

unread,
May 12, 2015, 11:16:51 PM5/12/15
to std-pr...@isocpp.org, pot...@mac.com
Hmm. Did you even read my proposal and understand the code? Your comments don't seem to address my proposal. std::function can't do equality comparison which has always been my biggest problem with it. Removes a lot of usefulness hence my proposal.

Houe

unread,
May 12, 2015, 11:44:46 PM5/12/15
to std-pr...@isocpp.org, pot...@mac.com
Equality comparison is the key feature and opens up so many more possibilities than std::function has. The basic idea is this:

void standalone_function(int a)
{
}

class some_class {
public:
   void member_function(int a) {}
}; 

int main()
{
   auto fp1 = make_fun(&standalone_function);
   fp1(1); //call standalone_function

   some_class foo;
   auto fp2 = make_fun(&some_class::member_function, &foo);
   fp2(1); //call member_function() on foo instance

   bool compare = (fp1 == fp2); // false

   auto fp3 = make_fun([](int) {
      //do something
   });
   fp3(1); //call the lambda

   bool compare2 = (fp1 == fp3); //false

   fp1 = fp2;
   bool compare3 = (fp1 == fp2); //true
}

Christopher Jefferson

unread,
May 13, 2015, 7:03:51 AM5/13/15
to std-pr...@isocpp.org, pot...@mac.com
On 13 May 2015 at 04:16, Houe <mboyk...@gmail.com> wrote:
> Hmm. Did you even read my proposal and understand the code? Your comments
> don't seem to address my proposal. std::function can't do equality
> comparison which has always been my biggest problem with it. Removes a lot
> of usefulness hence my proposal.

I have read your proposal, but I don't really understand what you are
getting at.

Is the only useful feature your tool adds equality comparison? What
does your wrapper lose in comparison with std::function?

Do you believe std::function could be efficiently extended with
operator==? (If that is the case, adding operator== there seems like a
better idea).

I feel there should be an extremely high bar to introducing a second
function wrapper (and std::function isn't going away soon, if ever).
Can you explain exactly what is wrong with std::function, and why it
cannot be fixed?

Chris
> --
>
> ---
> 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/.

Houe

unread,
May 13, 2015, 7:26:57 AM5/13/15
to std-pr...@isocpp.org, pot...@mac.com
Chris,

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. I also referenced N4159 which is a proposal to fix std::function's const incorrectness. In addition beside std::function's const incorrectness std::function performs a deep copy (copies the function object given to it) which is not the semantics of a pointer like type. fun_ptr (by combining parts of bind and function into a single class) is able to provide equality comparison, true function pointer like semantics (hence the class name), and better syntax (function + bind looks pretty ugly in comparison). I agree the bar needs to be very high to introduce a new std library feature. If there is not enough interest then this will remain just be a 3rd party library instead of a standard one.

David Krauss

unread,
May 13, 2015, 9:14:46 AM5/13/15
to std-pr...@isocpp.org
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.

Forgive me for overlooking that.

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).

That’s a leap of logic.

One quite feasible way to give std::function equality-checking capabilities on par with your utility, along with much other functionality, would be to extend its single signature to a variadic list:

typedef std::function< ret( arg ), std::shared_ptr< void * >( get_ptr_tag ) > equality_func;

bool operator == ( equality_func const & lhs, equality_func const & rhs )
    { return lhs( get_ptr_tag{} ) == rhs( get_ptr_tag{} ); }

Multiple-signature std::functions might not be mentioned in N4159, but I think they’re likely to happen sooner or later. The implementation is likely to use a vtable instead of a raw pointer, but otherwise they shouldn’t be much different from the existing single-signature case.

In my proposal I references the boost FAQ as to why std::function is unable to provide equality comparison.

That comparison semantic, using operator==, is completely different.

For what it’s worth, I think void *std::function::target< void >() might be nice. It would also enable this kind of identity checking, as long as you know both targets are standard-layout with compatible identifying info at the beginning. Possible to arrange for something like shared_reference, but still a narrow use-case in the grand scheme of things.

Arthur O'Dwyer

unread,
May 14, 2015, 2:03:46 AM5/14/15
to std-pr...@isocpp.org, pot...@mac.com
On Tuesday, May 12, 2015 at 8:44:46 PM UTC-7, Michael Boyko wrote:
Equality comparison is the key feature and opens up so many more possibilities than std::function has.
 
What would you suggest the output ought to be for

    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

or for that matter

    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

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.

–Arthur

David Krauss

unread,
May 14, 2015, 2:27:50 AM5/14/15
to Arthur O'Dwyer, std-pr...@isocpp.org

On 2015–05–14, at 2:03 PM, Arthur O'Dwyer <arthur....@gmail.com> wrote:


Ooh, more online compilers is better.

As the Boost FAQ says, the only viable operator== behavior for std::function is to form an equality expression on-demand (impossible) from two erased types (impossible).

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.

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>().

David Krauss

unread,
May 14, 2015, 2:31:20 AM5/14/15
to Arthur O'Dwyer, std-pr...@isocpp.org

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>().

Oops, actually you would avoid that issue, but at the cost of two indirect calls: the std::function inside the wrapper and the one outside. Workable, just a little sluggish.

Michael Boyko

unread,
May 14, 2015, 10:23:49 AM5/14/15
to std-pr...@isocpp.org, pot...@mac.com
Arthur,

Good examples that do need answers. fun_ptr answers these in a way consistent with function pointer like semantics.

John Lakos has a talk on the meaning of values (see youtube: Value Semantics: It aint about the syntax). Basically I took away from it 3 properties a value type should have:

A. be able to substitutable with a copy
B. If a and b have the same values and the same salient operations are performed on a and b then both objects will again have the same value.
C. Two objects of a given value-semantic type have the same value if and only if there does not exist a distinguishing sequence among all of its salient operations.

The definition for a salient operation is basically any operation that has an effect on the type's value. The example Lakos gives is a std::vector's size is a salient attribute, but its capacity is not. So reserve() and shrink_to_fit() would not be salient operations but resize(), push_back(), etc. would be salient operations.

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).

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.

For function pointer like semantics we want the copy to behave exactly like the original, right (property A above)? The following code should behave exactly the same no matter the output from rand(). And for fun_ptr it does - it calls the lambda 1000 times regardless of rand()'s output.

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();
}

If the above example was replaced with regular old function pointers what is the behavior we would want and expect? std::function does not behave like a regular function pointer would, but fun_ptr does!

Now let's consider your two examples with fun_ptr replacing std::function and then compare and discuss...

    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

Functors are the trickiest part of fun_ptr's implementation...

Scott Myers says in Effective STL (emphasis mine):
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.
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) but when fun_ptr is copied it shares that copy of the functor. After creating a fun_ptr with a functor the only way it will ever test equal with another fun_ptr is by making a copy of the original fun_ptr. This is why the rand example above works no matter if fun_ptr's target is a standalone function, a member function, or a functor!
   
    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)

Again calling f1() is const operation and not salient (doesn't change its target) and so the f1 has the same value after the call as it did before the call.

I will try to add some of these examples and explanations to my proposal - I don't think I am marketing this very well...

Message has been deleted

Arthur O'Dwyer

unread,
May 14, 2015, 12:33:13 PM5/14/15
to std-pr...@isocpp.org, mboyk...@gmail.com
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."


> For function pointer like semantics we want the copy to behave exactly like
> the original, right (property A above)? The following code should behave
> exactly the same no matter the output from rand(). And for fun_ptr it does -
> it calls the lambda 1000 times regardless of rand()'s output.
>
> 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();
> }
>
> If the above example was replaced with regular old function pointers what is
> the behavior we would want and expect? std::function does not behave like a
> regular function pointer would, but fun_ptr does!

shared_ptr<function<F>> behaves like (F*).


> Now let's consider your two examples with fun_ptr replacing std::function
> and then compare and discuss...
>
> 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

Yep, that corresponds with my interpretation of your proposal as well.
The fun_ptr constructor (and make_fun) create deep copies of their
argument, as if by make_shared<function<F>>(lam1).

> 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)

Yep, this corresponds with my interpretation of your proposal as well.
fun_ptr<F>, being a synonym for shared_ptr<function<F>>, has reference
semantics with relation to the controlled object of type function<F>.
The controlled object is created via deep copy when
make_shared/make_fun is called, but after that point, copying the
shared_ptr simply creates a new pointer to the single copy.

> Again calling f1() is const operation and not salient (doesn't change its
> target) and so the f1 has the same value after the call as it did before the
> call.

Calling f1() is not actually a const operation. In particular, after
the assignment

fun_ptr<int(void)> f1 = lam1;

calling f1() will affect a different copy of the functor object than
calling lam1() will — f1 is in no sense a "pointer to" lam1.
(Try it out and see!)


> 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
event.h

Michael Boyko

unread,
May 14, 2015, 12:44:56 PM5/14/15
to std-pr...@isocpp.org
Here is an updated document.
smart_function_pointer.zip

Michael Boyko

unread,
May 14, 2015, 1:30:25 PM5/14/15
to std-pr...@isocpp.org, mboyk...@gmail.com
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...


On Thursday, May 14, 2015 at 11:33:13 AM UTC-5, Arthur O'Dwyer wrote:
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.

fun_ptr has shared_ptr<function> semantics just for function objects. fun_ptr can also target member functions and standalone functions. make_fun(&some_class::some_function, &obj) or make_fun(&standalone_function) definitely cannot have shared_ptr<function> semantics - equality will not work.
 

[...]
> 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.

I'm not referring to operator() in the lambda. operator() in std::function is always const as well as in fun_ptr - that's what I was referring to. So yes operator() being const in std::function is kind of lying. That's what N4159 points out.
 

> 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."

For the case of functors only(!) fun_ptr does the following: Copy construction makes a copy of the lambda and copy assignment does not make another copy of the lambda (its shared via a shared_ptr). For member functions and standalone functions just copying of the target is performed. Equality works in all cases.
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.
 


> 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.

No. I pointed out why this is not the case: standalone and member functions. See above.
 

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.

I've looked at it but as I explained above this cannot work for uses of standalone and member functions. It will work like fun_ptr for functors I think.
 

HTH,
–Arthur

Michael Boyko

unread,
May 14, 2015, 1:48:37 PM5/14/15
to std-pr...@isocpp.org
Let me also say that even if the target of functors was completely removed so that fun_ptr only could target standalone and member functions it would still be almost as useful. Please don't get hung up on the functor target. Functors really are the 5% or less use case. The item to concentrate on is equality operator works as excepted and has improved syntax over combining std::function and std::bind.

Nicol Bolas

unread,
May 14, 2015, 3:59:54 PM5/14/15
to std-pr...@isocpp.org


On Thursday, May 14, 2015 at 1:30:25 PM UTC-4, Michael Boyko wrote:
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...

I find this all somewhat confusing. You say that the principle problem that func_ptr is intended to solve is the lack of an equality comparison. OK, but then you say this:


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.

Which means that the only functional differences between std::function and your proposed fun_ptr are:

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.

2) When storing functors, the object allocates memory, whose ownership is shared among all copies of that fun_ptr.

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.

The shared ownership of functors is, quite frankly, not something we need an object to handle. It's certainly not something that should be tied directly into comparison semantics. There's no reason why a user of std::function should have to choose between value semantics and (limited) operator== support.

In short, there's no reason why we need an object to represent the concept of a shared_ptr that owns a std::function. We have std::function, which has value semantics. We have shared_ptr, which has shared reference semantics. Just combine them.

Just like we don't need a std::vector that has shared reference semantics, rather than value semantics.

And there's an even better reason not to combine these concepts. You didn't do it very well.

You have fun_ptr, which is meant to represent shared_ptr<function>. But it doesn't; it's missing tons of stuff. Where's the weak_fun_ptr? What about allocation/deleter support; even std::function has that (well, the allocator part)?

In short, I'd be much better off using shared_ptr<function>, with a minor enhancement to std::function.

Nevin Liber

unread,
May 14, 2015, 4:17:18 PM5/14/15
to std-pr...@isocpp.org
On 14 May 2015 at 14:59, Nicol Bolas <jmck...@gmail.com> wrote:
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.

Any such proposal would have to answer the hairy question on what equality comparison should do (return, exception, etc.) if two std::function objects hold other callable types.  While you can extend such functionality to any homogeneous comparison (compare std::function objects if they hold the same type; otherwise return false), you still have to decide what to do if the types being held are not comparable.
--
 Nevin ":-)" Liber  <mailto:ne...@eviloverlord.com(847) 691-1404

Michael Boyko

unread,
May 14, 2015, 4:49:04 PM5/14/15
to std-pr...@isocpp.org
I find your comments constructive. I think I may have made a mistake of allowing functors as a target of fun_ptr. That feature does have a few quirks and probably limited uses. But here are some examples that might help you understand my decisions.

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);

compare should be false because fp1 and fp2 point to different instances of Foo. This is the correct pointer like semantics we want.

Now consider this:

auto foo = [=]() {/*...*/};
fun_ptr
<void()> fp1(foo);
fun_ptr
<void()> fp2(foo);

compare
= (fp1 == fp2);

What should compare be now? I chose false because functors are pass by value which results is two separate instances of the lambda. This is basically the same as the first example, right? So why should comparing fun_ptr's with functor targets not consider which instance it points to? I considered passing functors by reference but this would be an anti-pattern and lead to lifetime issues when functors are often temporary objects. Removing functor targets might be better than deciding to make a copy and managing it. Or possibly only allowing non-capturing lambda's that can convert to a function pointer and then handle them the same as standalone functions.

Michael Boyko

unread,
May 14, 2015, 4:52:39 PM5/14/15
to std-pr...@isocpp.org
This is why boost decided not to implement equality comparison - They did not want a run time failure and can't do it at compile time.

Nicol Bolas

unread,
May 14, 2015, 5:05:46 PM5/14/15
to std-pr...@isocpp.org
I guess the fundamental issue I have with comparing arbitrary function objects of this type is the answer to this question:

What exactly are the use cases where you're asking two function objects if they're the same?

We're talking about opaque callables here. You have two functions, who's implementations could come from anything. So what is the point of testing if one is equal to another? What are you trying to achieve by doing so.

I have never been in a situation where I had some need to know if one std::function I had received was equivalent by some measure to another. Your proposal also doesn't seem to explain the need for such a feature. It simply declares, "Equality comparison is a fundamental operation a smart function pointer needs to support," as though it were an undeniable fact that people obviously have need to compare two arbitrary callables.

When you said "Equality comparison is the key feature...", you go on to show an example of its use. But this doesn't explain the need for it.

The closest your proposal gets is an example of an event handling interface. The function you pass in to attach it to the container is also used as the handle to unregistering the function. But personally, I prefer to use a handle returned from the registration function. That way, I can keep around a small handle object rather than this big, bulky std::function. It also makes the interface more clear, if you're allowed to do things like swap which function is registered, or add other attributes to a specific registration (like a priority or whatever).

So why do you need to compare two arbitrary callables that have been given to your piece of code?

Michael Boyko

unread,
May 14, 2015, 5:22:58 PM5/14/15
to std-pr...@isocpp.org
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. Yes, there are work-arounds to not having direct comparison support and RAII handles are nice. We could remove function pointer comparison for standalone and member functions and people would survive but they are useful tools. C# has delegates and events which have proved extremely useful.

Nicol Bolas

unread,
May 14, 2015, 6:39:40 PM5/14/15
to std-pr...@isocpp.org


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.

Here's what I mean. In C#, functions are... functions. They're references to existing code. They cannot have state. So equality is very simple.

In C++, any "polymorphic function" interface ought to be able to handle functors. Function objects are extremely commonly used in a variety of C++ programming styles; indeed, this is why std::function is specifically designed to permit their use.

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.

Let's take your own example, with one minor modification:

auto foo = []() {/*...*/}; //Notice something missing.

fun_ptr
<void()> fp1(foo);
fun_ptr
<void()> fp2(foo);

compare
= (fp1 == fp2);

There is no reason for these two functors to be considered different in any way. In fact, I didn't even have to remove the "=" capture; I could have simply stated that it didn't happen to capture anything from the enclosing scope.

Most users would expect to be able to do this:

event.register(fp1);
event.unregister(fp2);

That code would have worked just fine if these were actual function pointers. But because the user just so happened to store a lambda instead of a function pointer, the semantics change.

From a user perspective, that is surprising. And that's a very bad thing to surprise a user with.

If equality comparison between functions is as critical as you claim, then you shouldn't do it half-way. You can't say that it's important for function pointers, but that you can arbitrarily declare all functors to be unequal. That's simply not a workable solution.

Plus, there's the fact that C++ functors often have state. And in some cases, the functor is the sole owner of that state. So... why should the user have to keep a heavyweight object around, or even a "smart pointer" reference to one? The user isn't going to call that functor anymore; they're only keeping it around to use as a handle. The user should be able to move a std::functor into the event object, with the event being the sole owner of it.

That's why the Boost.Signal approach is superior for C++; you don't have to answer the question of whether different callable values are equal or not. You simply return a handle that is used to represent the attached callable. If the user happens to register the "same function" (by some definition) twice, then it should be assumed that this is what the user intended to do (which can be reasonable in some cases).

C++ is not C#, and therefore is going to have to do things differently. What matters is that you can do the same things, not that the interfaces are 100% identical.

And thus far, you have not proven that equality comparison is an essential feature for polymorphic functions.

Nevin Liber

unread,
May 14, 2015, 7:09:26 PM5/14/15
to std-pr...@isocpp.org
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?

Nicol Bolas

unread,
May 14, 2015, 8:29:54 PM5/14/15
to std-pr...@isocpp.org
On Thursday, May 14, 2015 at 7:09:26 PM UTC-4, Nevin ":-)" Liber wrote:
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."

Sure. Though it might be considered odd by the user if two different functors compare equal normally, yet don't compare equal when wrapped by std::function.
 
 
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.

Always good to hear.
 
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...

David Krauss

unread,
May 14, 2015, 10:00:07 PM5/14/15
to std-pr...@isocpp.org
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.

Should be solved regardless of std::function.

mash.bo...@gmail.com

unread,
May 14, 2015, 10:41:29 PM5/14/15
to std-pr...@isocpp.org


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.

Hope that helps.
 

mash.bo...@gmail.com

unread,
May 14, 2015, 10:46:16 PM5/14/15
to std-pr...@isocpp.org


On Thursday, May 14, 2015 at 6:09:26 PM UTC-5, Nevin ":-)" Liber wrote:
auto l = []{ /* ... */};
auto f = +l;

are f and l equal?

Not only are equal but comparing l and f is currently valid C++ code. 

mash.bo...@gmail.com

unread,
May 14, 2015, 11:13:51 PM5/14/15
to std-pr...@isocpp.org
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. Consider data pointers:

int i1 = 1;
int i2 = 2;

int* p1 = &i1;
int* p2 = &i2;

bool compare1 = (p1 == p2); //false
i2 = 1;
bool compare2 = (p1 == p2); //still false

I'll submit that the internal state of the functor being pointed to is likewise don't care for a function pointer. Consider the alternative that equality did depend on the state of what its pointing to. This would mean that equality would be volatile since another thread might change its state.

I'd point you to Lakos who deals with this here at time 51mins.

Nevin Liber

unread,
May 14, 2015, 11:14:32 PM5/14/15
to std-pr...@isocpp.org
On 14 May 2015 at 20:59, David Krauss <pot...@mac.com> wrote:

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).

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?

David Krauss

unread,
May 15, 2015, 1:03:08 AM5/15/15
to std-pr...@isocpp.org
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?

The obstacles are not being able to know about classes outside the current TU, the quadratic number of potential type combinations within a TU, and the difficulty of metaprogramming to even find those combinations. And to a lesser extent, the possibility that the user will instantiate std::function before declaring a non-member operator==. These are compile-time problems.

By explicitly subscribing to an EqualityComparable concept, a type can declare that operator== will be defined.

A more elaborate trait can specify the comparison function to be used, avoiding the problem of overload resolution. The function couldn’t be templated or overloaded, but it could take a reference to a base class. The erasure would include a pointer to the comparison function. Upon comparison, if the function pointers are equal, then the objects agree on what to do, even if they have different types.

Constructors for std::function could even accept comparator function pointer arguments, and that could even be an alternative to a trait. But, I think the trait interface is easier and provides less potential for surprise.

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?

Your question seemed to rhetorically say that the meaning isn’t obvious, so I didn’t reflect much on the actual behavior. Yeah, now I see that it’s reasonable. The default comparison operator in a captureless lambda (like any empty class) would always return true, and comparisons that include lambda-to-fptr conversions essentially tell you whether two lambdas are of the same type.

Bringing in the type trait idea, captureless lambdas could uniformly nominate function-pointer comparison as the post-erasure comparison semantic. So we could have:

// By default, no heterogeneous comparison.
template< typename t, typename = void >
struct post_erasure_comparison_traits {
    static bool comp( t const & a, t const & b )
        { return a == b; }
};

// A member typedef post_erasure_compare_as nominates an implicit cast to make heterogeneous objects comparable.
template< typename t >
struct post_erasure_comparison_traits< t, std::conditional_t< true, void, 
    typename t::post_erasure_compare_as > >
    : post_erasure_comparison_traits< typename t::post_erasure_compare_as >
    {};

  • Captureless lambdas would define a member typedef __signature *post_erasure_compare_as;.
  • Derived classes could nominate a reference to base class type, perhaps with a virtual operator==.
  • A shared_ptr wrapper solving the OP problem could nominate std::shared_ptr<void*>.
  • Types where std::function shouldn’t use operator== can specialize the traits class to refer to something else.

Not universal, effortless heterogeneous comparison, but it should cover the important cases.

Nicol Bolas

unread,
May 15, 2015, 1:08:04 AM5/15/15
to std-pr...@isocpp.org, mash.bo...@gmail.com


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.

Nicol Bolas

unread,
May 15, 2015, 1:15:40 AM5/15/15
to std-pr...@isocpp.org, mash.bo...@gmail.com
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.

David Krauss

unread,
May 15, 2015, 1:57:40 AM5/15/15
to std-pr...@isocpp.org
Oops, my code neglected to handle non-comparable types.

// No comparability => always compare false. Could throw if that’s preferable.
template< typename t >
struct post_erasure_comparison_traits< t, std::enable_if_t< ! is_equality_comparable< t, t > > {
    static bool comp( anything_t, anything_t )
        { return false; }
};

Disabling this specialization when post_erasure_compare_as is defined is left as an exercise to the reader ;) . Seriously though, shouldn’t be too much implementation effort if this direction is taken.

Brent Friedman

unread,
May 15, 2015, 3:48:23 AM5/15/15
to std-pr...@isocpp.org
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.

The use case is some UI menu code. Each object implements a sort of visitor pattern to describe valid menu commands.

void A::getCommands( CommandConsumer& c)
{
   if (CanDelete())
      c.add( "Delete", []{ ... } );
   c.add( "Copy", []{ ... } );
}

void B::getCommands( CommandConsumer& c)
{
   A::getCommands(c);
   c.add( "Instantiate", []{ ... } );
}

So the CommandConsumer will be left with a mapping from objects to commands. In the case where we multi-select several objects and invoke the UI menu, it is very likely that some of these commands will be supported by multiple objects. In the above example, A's commands will be available for Bs and for As.

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.

Ways to work around the issue:
1: augment CommandConsumer with an additional function which takes a function pointer instead of a std::function. Internally it will store a variant< function pointer, function>. This provides an escape hatch to work around the equality comparison problem, but is not a general solution.
2: add a guid parameter to CommandConsumer::add -- explicitly defining equality among things with the same guid. This is a solid solution that will be annoying for users of the API.

Workaround 1 presents the argument for equality comparison of function pointers but having not yet written that code I can't say for certain its value. I don't anticipate that a smart function pointer will really be needed in this example.



--

---
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/.

David Krauss

unread,
May 15, 2015, 3:57:10 AM5/15/15
to std-pr...@isocpp.org

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.

I don’t see how these functions would be comparable. I do see how the strings (command names) would be comparable.

You mention a GUID, but what would happen if two functions have the same user-visible name and different GUIDs? Or same GUIDs and different names?

Michael Boyko

unread,
May 15, 2015, 9:10:53 AM5/15/15
to std-pr...@isocpp.org, mash.bo...@gmail.com


On Friday, May 15, 2015 at 12:08:04 AM UTC-5, Nicol Bolas wrote:


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.

Pointers compare memory addresses not values. Data/object pointers, function pointers and member function pointers all compare memory addresses only. Why would you want to change that long established pattern? Now admittedly a functor is a strange animal - is it a function or is it an object? Is light a particle or a wave? So yes I do understand where some might like to compare it like an object (internal state). However going down the path where a pointer type compares values of what it points to is heading down the wrong road in my opinion.
 

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.

This might be true and I have pointed out that even removing the functor targets leaves 95% of the usefulness intact. My last two companies have used this type successfully without any lambda targets. Unfortunately this thread is now only focused solely on the 5%. I guess that in itself is helpful information.
 
 

Michael Boyko

unread,
May 15, 2015, 9:17:13 AM5/15/15
to std-pr...@isocpp.org


On Thursday, May 14, 2015 at 7:29:54 PM UTC-5, Nicol Bolas wrote:
 
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...

How about this:

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?




Michael Boyko

unread,
May 15, 2015, 9:37:53 AM5/15/15
to std-pr...@isocpp.org, mash.bo...@gmail.com


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:
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.

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. Lambda functions are syntactic sugar - not needed, but a nice feature nonetheless.
 
 
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.

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?
 
 

Nicol Bolas

unread,
May 15, 2015, 9:52:09 AM5/15/15
to std-pr...@isocpp.org, mash.bo...@gmail.com

I'm sure they did. Though since lambdas are relatively new, and this pseudo-polymorphic function is relatively old, people just find ways to work around it. That doesn't mean that people should.

After all, lots of people refuse to use std::string or other standard library classes, instead rolling their own solutions that may have more limited (or at least different) interfaces than the standard library versions. Sometimes, these are for very good reasons (iostream). Sometimes, they aren't. But the fact that they do so by itself is not an argument that something is wrong with the standard library classes.

If you take functors out of the concept... what's left? It certainly isn't a polymorphic function object. It's just a function object that could store a non-member function pointer or a member function pointer+object reference.

Even the "shared" aspect becomes moot. You can't really claim ownership over a function pointer. And member pointer bindings are given a pointer to the type to call, it can't really claim ownership over that pointer either, since the user may not be transferring said ownership to you. It is a naked pointer, after all.

Is there a use for such a limited "polymorphic" function object? Sure. Is it sufficiently general of a use that it should be standardized?

Michael Boyko

unread,
May 15, 2015, 10:10:35 AM5/15/15
to std-pr...@isocpp.org, mash.bo...@gmail.com
Saying it another way: fun_ptr = std::function + std::bind + equality comparison - probably functor targets.

You question is a fair one. Would such a tool be useful enough to standardize? That's why I've posted it here...

Nicol Bolas

unread,
May 15, 2015, 10:17:12 AM5/15/15
to std-pr...@isocpp.org, mash.bo...@gmail.com
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.

Nicol Bolas

unread,
May 15, 2015, 10:44:13 AM5/15/15
to std-pr...@isocpp.org
On Friday, May 15, 2015 at 3:48:23 AM UTC-4, Brent Friedman wrote:
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>

That's an interesting problem. However, I have some questions about the problem space that should help show the problem with your current interface.

Your `add` function associates a functor with a name. However, your algorithm also seems to care about which object was responsible for making that association. Yet your interface has no knowledge of this.

It seems to me that you already conceptually have a GUID: the name for the function and the object that was responsible for registering it.

Now, if you have two different objects that register with the same name, what happens rather depends on the meaning behind everything. You could think of it in an inheritance pattern, such that new registrations are like virtual functions, overriding the old. Or you can do it via composition, where both are contacted. Or you could see it as an error, throwing an exception if someone tries to register the same name twice. Personally, I prefer option 2, as it makes the most sense.

But ultimately, I don't see how being able to compare functors would help you in this instance. Any sorting you do would be based on the registered name and source object types, both of which are properties that are separate from the function itself.

Which only helps support the premise that comparing functions is not the right thing to do. What you need to compare is registration properties, which is a rather higher level construct.

Michael Boyko

unread,
May 15, 2015, 11:43:48 AM5/15/15
to std-pr...@isocpp.org, mash.bo...@gmail.com
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.

Brent Friedman

unread,
May 15, 2015, 1:33:23 PM5/15/15
to std-pr...@isocpp.org
It seems to me that you already conceptually have a GUID

Yes, the name of the command certainly could count as a GUID in my example. The nature of this being a user interface system presents a pretty strong reasoning for such as it would be a poor UI that had different commands with the same name. Relying on plain text as a guid is probably not good practice in general but makes decent sense in this particular example.

 
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.

--

Michael Boyko

unread,
May 15, 2015, 2:01:24 PM5/15/15
to std-pr...@isocpp.org


On Friday, May 15, 2015 at 12:33:23 PM UTC-5, Brent Friedman wrote:
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.

 
I agree my example might be a bit contrived but the main point is when different objects perform the attach and detach it would be nice to not have to go searching for the handle to perform the disconnect. Sharing a connection handle among many objects seems rather ugly. Most likely similar examples are possible with GUI widget interactions.

Nicol Bolas

unread,
May 15, 2015, 2:06:06 PM5/15/15
to std-pr...@isocpp.org, mash.bo...@gmail.com
On Friday, May 15, 2015 at 11:43:48 AM UTC-4, Michael Boyko wrote:
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.

How could B possibly know that it needed to detach itself from E... if B were not informed that it had been attached to E?
 
2. Maybe A would do the detach or Maybe B would do the detach whoever has reason to do it first.

Yes, and in a similarly designed system, A might try to free memory owned by B, thus creating a problem when B goes to try to free that memory too.

We have a solution to these: shared_ptr. If you think that this is so important a problem (I don't, because the example seems rather contrived), then clearly there needs to be a concept of shared ownership of connections: shared_connection.
 
3. Maybe another Object C comes into existence and wants to detach B from E.

... I'm sorry, what? The idea that object C has any right to detact object B from E, when C has no established relationship to A or B, is absurd. Why would you ever write code that does this? How would this pass code review?
 
Where should  this connection objects live? Maybe some global object?

You handle connections just like any other resource that needs termination: you made it, you own it. If someone else needs to own it, you transfer ownership to them. If you need to share ownership, you develop that ability. And so forth.

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.

OK, let's take that as an example. Let's say that we have object C that puts a debuff on B. And you want to model this as an event. Well... that debuff itself must be an object. It must be something more than just a naked pointer, because you're going to have to have many other things interacting with the debuff.

For example, you need a UI element to appear when the debuff is active. Are you really going to run through the player object and look for specific functions for specific debuffs, then have some table mapping function pointer addresses to UI images? No; that's way too fragile and incredibly absurd. You look through each object's list of active debuffs, and the debuff type will reference a specific UI image (as well as other debuff properties).

It is the setting of this debuff on an entity that sets up any events that are important for making it work. And it is the act of removing that debuff from an entity that removes those events.

Object B has no right to be poking about at events in object A directly. It can add or remove debuffs, but debuffs (and similar objects) are the ones who are allowed to manipulate the actual events.

Nicol Bolas

unread,
May 15, 2015, 2:07:48 PM5/15/15
to std-pr...@isocpp.org

What's "rather ugly" about it? It makes more sense than various unrelated GUI widgets assuming which public member functions they're all registering. At least the ownership relationships for connections are clear.

Michael Boyko

unread,
May 15, 2015, 2:18:29 PM5/15/15
to std-pr...@isocpp.org, mash.bo...@gmail.com
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.

Nicol Bolas

unread,
May 15, 2015, 2:41:18 PM5/15/15
to std-pr...@isocpp.org, mash.bo...@gmail.com
On Friday, May 15, 2015 at 2:18:29 PM UTC-4, Michael Boyko wrote:
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.

Let's say that object B has the right to attach object A to an event. OK, fine.

But rights come with responsibilities. If you design a system such that object B has the right to attach some other object A to events, then object B must take on the responsibility to manage that attachment. It needs to make sure that A is not destroyed before the connection is broken, for example. Because B has done this without A's knowledge, this responsibility must rest on B's shoulders, since it got A involved.

There are several ways to handle this. Maybe B stored a shared_ptr<A> in the event. Maybe B tells A that it has been connected, so its destructor can disconnect it (which also requires that it be able to detect if the event E is destroyed). Maybe there's some higher-level construct that is informed by B that a connection between A and E has been formed, so it will destroy them when A is being destroyed. Or maybe you have an a priori assumption that A will always outlive whatever event objects it gets attached to.

That last one? I don't see how that is "valid design" in most cases. It's certainly not something that should be encouraged or made the default case. Ignoring one's responsibilities and pretending they don't exist is not an effective solution to most problems.

Oh, and here's an interesting way to handle it. Instead of registering a member pointer + shared_ptr<A> (assuming your `fun_ptr` can even handle that), you instead register a lambda that looks like this:

[ptr = weak_ptr<A>{smart_ptr}](...)
{
  shared_ptr
<A> p = ptr.lock();
 
if(p) { return p->MemberFunction(...);}
 
return {};
}

Oh but that's right; you can't register a lambda with your `fun_ptr` type. Too bad; either your event system handles weak relationships manually (which is admittedly reasonable... though you wouldn't be able to use your `fun_ptr` type to do it), or you're SOL.

Nevin Liber

unread,
May 15, 2015, 5:36:10 PM5/15/15
to std-pr...@isocpp.org
On 14 May 2015 at 19:29, Nicol Bolas <jmck...@gmail.com> wrote:
Ugh. How many other gotcha cases are there with regard to function equality?

You want another one?  Take the following generic functor:

template<typename T>
struct Functor
{
    explicit Functor(T t) : data(std::move(t)) {}

    void operator()() const { return data(); }

    friend bool operator==(Functor const& l, Functor const& r)
    { return l.data == r.data; }

    T data;
};

What will a type trait / concept for equality comparable return for T?
  1. Yes
  2. No
  3. Depends on T
What will a type trait / concept for equality comparable return for Functor<T>?
  1. Yes
  2. No
  3. Depends on T
Feel free to try it with, say, has_equal_to, and then think about the implications...


As the expression goes, this is just like shooting fish in a barrel...
-- 

David Krauss

unread,
May 15, 2015, 7:29:07 PM5/15/15
to std-pr...@isocpp.org
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?
  1. Depends on T
… And if No, it’s a hard failure for Functor.

What will a type trait / concept for equality comparable return for Functor<T>?
  1. Yes
… Because it’s an intrinsic requirement of Functor.

Feel free to try it with, say, has_equal_to, and then think about the implications…

This how template argument requirements have always worked: duck-type specification, internal failure on violation. You “just” have to disable the operator== declaration, with SFINAE or Concepts, if it’s supposed to be optional.

Will someone see a comparable std::function cause such errors? Probably. But the code was arguably broken in the first place. The same failure mode will be exposed by implicit operator==, any time it sees a Functor<T> member.

David Krauss

unread,
May 15, 2015, 7:41:52 PM5/15/15
to std-pr...@isocpp.org

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.

Nevin Liber

unread,
May 16, 2015, 12:34:16 AM5/16/15
to std-pr...@isocpp.org
On 15 May 2015 at 18:41, David Krauss <pot...@mac.com> wrote:
… 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.

I guess we'll find out for sure when you propose this "improvement" to std::function...

David Krauss

unread,
May 16, 2015, 12:49:18 AM5/16/15
to std-pr...@isocpp.org

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…

Unfortunately, I’m not convinced of its motivation. Personally, I’ve wanted target<void> in the past. But I’ve never wanted to compare std::functions.

Anyone who wants to pick up the ball is free to run with it.

David Krauss

unread,
May 16, 2015, 2:03:33 AM5/16/15
to std-pr...@isocpp.org

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.

The user would associate the type to be erased with a type-list of tags mapping to compile-time constants. The implementation would turn that into a type_index-based map inside the erasure.

struct thing : foo {
    // Register compile-time constants for post-erasure availability:
    static constexpr auto erasure_info = static_tag_map(
        { std::comparison_fptr_tag{}, & compare_foos },
        { kind_tag{}, enum classifications::thing }
    );

    void operator();
};

// Erase the type a couple ways:
std::function< void() > f = thing{};
std::any a = thing{};

// Still get the data back:
assert ( f.get< kind_tag >() == a.get< kind_tag >() );

// It’s really a runtime thing now:
try {
    f.get< some_other_tag >();
} catch ( std::bad_erasure_info & ) {
    std::cout << "Erasure entry not found." );
}

Implementing this would be a fair bit harder, but it would supply sorely-needed polymorphism to all type erasure facilities.

Nicol Bolas

unread,
May 16, 2015, 2:38:40 AM5/16/15
to std-pr...@isocpp.org, pot...@mac.com


On Saturday, May 16, 2015 at 2:03:33 AM UTC-4, David Krauss wrote:

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>

That all seems suspiciously like something reflection ought to be able to handle.

David Krauss

unread,
May 16, 2015, 2:44:45 AM5/16/15
to std-pr...@isocpp.org

> On 2015–05–16, at 2:38 PM, Nicol Bolas <jmck...@gmail.com> wrote:
>
> That all seems suspiciously like something reflection ought to be able to handle.

Which part? Compile-time reflection can’t see an erased type.

Nicol Bolas

unread,
May 16, 2015, 3:14:56 AM5/16/15
to std-pr...@isocpp.org, pot...@mac.com

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.

David Krauss

unread,
May 16, 2015, 3:18:45 AM5/16/15
to std-pr...@isocpp.org

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.

There are various ways of doing that interface, I just chose something simple for illustration.

Also, the member .get<tag>() might not be a great interface either. That pseudocode is only a serving suggestion.

Michael Boyko

unread,
May 16, 2015, 11:47:41 PM5/16/15
to std-pr...@isocpp.org, mash.bo...@gmail.com


On Friday, May 15, 2015 at 12:08:04 AM UTC-5, Nicol Bolas wrote:


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.

After careful consideration of the discussion here I have changed my mind - state is what is important for functor equality. A few thoughts based on this:

1. mutable functors seems to be a different class than non mutable functors. I'm not sure or not if they fall under function pointer semantics. It would be akin to a function pointer changing each time it was invoked.

2. There is a proposal to make std::function thread safe by removing support for lambda/functors that do not have a const operator(). In light of this if only non mutable lambda's are considered then type alone is all that would be required guarantee equality. Said another way if two non mutable lambda functions have the same type they have the same behavior. I think that is correct. The problem is there currently is not a way to distinguish a lambda from a user defined functor.

3.  operator== should probably be noexcept and if equality can't be determined for a particular class of function types the result would then be unspecified. This follows the pattern c++ adopted for comparing virtual functions. This then allows future c++ features (concepts, default==, type traits, etc.) to possibly support another class of functions without breaking code. The result of the compare simply changes from unspecified to having a definite defined result which is a non breaking change. If it were to throw an exception and then its wanted to change from throwing to have a definite result that would be a breaking change.

So as of now standalone, member, lambda's that convert to function pointer, and user defined functors that would define operator== have a clear path of allowing operator==.

mash.bo...@gmail.com

unread,
May 17, 2015, 12:34:37 AM5/17/15
to std-pr...@isocpp.org, mash.bo...@gmail.com
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 false
2. else if targets are standalone funtions return true if function pointers match
3. else if targets are member functions return true if member function pointers and object instance pointers match
4. else if targets are functor/lambda
   A. If functor/lambda types are different return false
   B. else if operator== is defined return its result
   C. else return true


Nicol Bolas

unread,
May 17, 2015, 1:14:37 AM5/17/15
to std-pr...@isocpp.org, mash.bo...@gmail.com
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 false
2. else if targets are standalone funtions return true if function pointers match
3. else if targets are member functions return true if member function pointers and object instance pointers match
4. else if targets are functor/lambda
   A. If functor/lambda types are different return false
   B. else if operator== is defined return its result
   C. 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!"

Ville Voutilainen

unread,
May 17, 2015, 1:29:48 AM5/17/15
to std-pr...@isocpp.org
On 17 May 2015 at 08:14, Nicol Bolas <jmck...@gmail.com> wrote:
> 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

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.

David Krauss

unread,
May 17, 2015, 4:52:55 AM5/17/15
to std-pr...@isocpp.org, Geoff Romer

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.

Seems to me, the obvious solution would be to opt-in to synchronization through functor adaptor classes.

There was a “don’t add internal synchronization” decision. But an adaptor would only be used to fix the specific problem, outside std::function.

Only a small minority of std::functions with non-const call operators actually ever face a race condition. They can be internally synchronized, but 99.9% just aren’t shared across threads. Existing data races in the wild won’t be fixed by an opt-in. But fixing races in the wild without massive breakage is rather a tall order in general.

Ville, what do you think? Is there really an imperative to fix concurrency within std::function itself, or would the motivation of N4348 apply here? See implementation below.


#include <mutex>

// Library proposal:

template< typename ftor, typename mutex >
class basic_critical_section {
    ftor f;
    mutable mutex m;
public:
    basic_critical_section( ftor in )
        : f( std::move( in ) ) {}

    basic_critical_section( basic_critical_section const & o )
        : f( [&o] {
            std::lock_guard< mutex > g( o.m );
            return o.f; // Return by value. Accomplish the copy before the lock is released.
        }() )
        {}

    basic_critical_section( basic_critical_section && o )
        : f( [&o] {
            std::lock_guard< mutex > g( o.m );
            return std::move( o.f );
        }() )
        {}

    template< typename ... arg >
    decltype(auto) operator () ( arg && ... a ) {
        std::lock_guard< mutex > g( m );
        return f( std::forward< arg >( a ) ... );
    }
};

template< typename ftor >
using critical_section = basic_critical_section< ftor, std::mutex >;

template< typename ftor >
using recursive_critical_section = basic_critical_section< ftor, std::recursive_mutex >;

// TODO: make_critical_section() factory.


// Demo:

#include <thread>
#include <atomic>
#include <unistd.h>
#include <iostream>

std::atomic< int > cnt;
thread_local int const tid = cnt ++;

int main() {
    auto fn = [] { std::cout << tid << '\n'; ::sleep( 1 ); };
    critical_section< decltype(fn) > cs( std::move( fn ) );
    std::function< void() > f = cs;

    for ( int i = 0; i != 3; ++ i ) {
        new std::thread( [&] { for (;;) { f(); } } );
    }

    ::sleep( 10 );
    std::exit(0);
}

mash.bo...@gmail.com

unread,
May 17, 2015, 11:23:03 AM5/17/15
to std-pr...@isocpp.org, mash.bo...@gmail.com


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 false
2. else if targets are standalone funtions return true if function pointers match
3. else if targets are member functions return true if member function pointers and object instance pointers match
4. else if targets are functor/lambda
   A. If functor/lambda types are different return false
   B. else if operator== is defined return its result
   C. 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:
   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. Connection objects

On another note. I'd also like to get rid of is:

void fn(int a) { ... }

std::function<void(double)> fp1 = &fn; //really?

That does not seem like a strongly typed universal function pointer.

David Krauss

unread,
May 17, 2015, 11:56:25 AM5/17/15
to std-pr...@isocpp.org

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.

It’s not strongly typed, it’s type-erased. If you want to observe the type, check target_type().

Breaking your example would break all implicit conversions. Perhaps narrowing conversions deserve a crackdown, but the example isn’t even that.

Nicol Bolas

unread,
May 17, 2015, 1:36:19 PM5/17/15
to std-pr...@isocpp.org, mash.bo...@gmail.com


On Sunday, May 17, 2015 at 11:23:03 AM UTC-4, mash.bo...@gmail.com wrote:


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 false
2. else if targets are standalone funtions return true if function pointers match
3. else if targets are member functions return true if member function pointers and object instance pointers match
4. else if targets are functor/lambda
   A. If functor/lambda types are different return false
   B. else if operator== is defined return its result
   C. 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.

There's a reason why we invented RAII, smart pointers, and explicit ownership of objects in C++. To arbitrarily forbid such management in a feature as important as delegates seems very narrow-minded.


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:

I'm going to deal with your "good reasons" in turn, but I keep bringing up a point that you keep ignoring it.

You're always talking about events/delegates/signals. This is the only example you have for where comparing functions is useful.

So let's pretend for a moment that your "good reasons" actually are good reasons. Let's take it as a given that the correct interface for events/delegates/signals, the only one worth using, is based on "function" comparison.

So... why does std::function need to support comparison? Or why do we need to invent a new type that does? If the only reason you need to compare functions is for your perfect event/delegate/signal system, then just build that into its interface:

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);
};

There is nothing about this interface which requires that the user explicitly be exposed to the comparability aspect. That would purely be an implementation detail. And that way, you can define whatever rules you want, without affecting std::function or adding a new type or anything of the like.

You can require that functors have certain properties. You can test them, as I did above via concepts. And so forth.

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.
 
   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.

I want to restate your objection to a connection object. You're saying that using a connection object requires that the place where the attachment is made and the place where the attachment is severed have some sort of dependency. That there must be some means of communication between them.

But... that dependency already exists. Why? Because both places need access to whatever function was registered.

If it was a non-member function pointer, then this function needs to be accessible to both. It can't be a file-static function unless both places are in the same file. It can't be a private static member unless both places are in the same object.

If you registered a member+pointer, then not only do you need access to the member being registered (and therefore, you can't make it private), you also need access to the exact pointer that was registered. And not a derived class or base class type either; it's got to be value-identical.

So clearly there must already exist some means of communication between the attacher and the detacher.

Now, to be fair, there is one difference: a specific connection object can only be used with the specific event that it was made by. A function pointer alone can be used by any event object, while a connection object is event-specific.

Personally, I'd call that a feature, not a bug. But that's for other reasons (see below).
 
   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.

... I don't know what you mean by that. Presumably, any connection-based interface could be used to fetch the std::function object that was registered to the event/delegate/signal. And I don't know what other "information" would be associated with the connection that you might want to query.

   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).

The only advantage of such a system is that it would allow two different codebases to use two different coding styles. And I fail to see how that's a good thing.

Other points in favor of connections:

4) You don't have to expose implementation details, like pointers to the exact object being registered. What does the code that detaches the attachment care exactly what kind of thing was attached? Why should that code have to be modified just because you decided to switch from a non-member function to a member+instance? Or if you decide to wrap what used to be a regular function pointer in a lambda?

This is especially true once you start shoving lambdas into it. Because it means that, if I attach a lambda to an event... then whoever it is that detaches that lambda needs access to the exact lambda that I created. Not merely a lambda function that does the same thing. It must be the exact same lambda, since every lambda declaration is its own type.

So now, I can't really use function-local lambdas; my lambda has to be externally visible to the function. At which point, it loses about 90% of the advantages of being a lambda in the first place; it may as well be a normal functor.

Connection objects promote better encapsulation.

5) The functor doesn't have to be `const`. Remember: your method relies upon N4348. And even though Ville apparently wants to do his best to ensure that this doesn't happen, you can still do what I suggested above and write an event system that doesn't rely on an externally-visible class. So your event class can delcare that only `const` functors are allowed, presumably so that you can implement comparisons.

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{});

This seems like a rather arbitrary and absurd distinction to make.

The only reason you don't allow functor state modification is to make comparing functors reasonable. But if we use connection objects to manage connections, then we don't have to care about comparing std::function objects. At which point, it is perfectly possible to register non-`const` functions to events/delegates/signals.

Connection objects promote more sane APIs with regard to functors.

C++ isn't C# or Java or Python. Functors are really important in C++, and any event system worthy of being in the standard library needs to support them just as effectively (if not moreso) as it supports member pointer+instances or whatever.

And if providing that support requires that C++ events/delegates/signals must use explicit connection objects rather than passing the function again, then that's the interface C++ should use.

Nicol Bolas

unread,
May 17, 2015, 1:42:11 PM5/17/15
to std-pr...@isocpp.org, pot...@mac.com

I think you read the conversion backwards. The std::function's interface takes a `double`, which must be converted to an `int` for consumption by the function pointer.

And double-to-int most certainly is narrowing.

David Krauss

unread,
May 17, 2015, 1:49:23 PM5/17/15
to std-pr...@isocpp.org

> On 2015–05–18, at 1:42 AM, Nicol Bolas <jmck...@gmail.com> wrote:
>
> I think you read the conversion backwards. The std::function's interface takes a `double`, which must be converted to an `int` for consumption by the function pointer.
>
> And double-to-int most certainly is narrowing.

Derp.

So we have a narrowing conversion… is someone proposing to diagnose these?

mash.bo...@gmail.com

unread,
May 17, 2015, 5:28:39 PM5/17/15
to std-pr...@isocpp.org, mash.bo...@gmail.com
This isn't the worst idea and in fact I did write one of these to hide the implementation (older macro based delegate system I created a while back). Writing the RAII scoped connection object turned out to be a bit more difficult, but doable.
 

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?

mash.bo...@gmail.com

unread,
May 17, 2015, 5:35:55 PM5/17/15
to std-pr...@isocpp.org, mash.bo...@gmail.com


On Sunday, May 17, 2015 at 12:36:19 PM UTC-5, Nicol Bolas wrote:

   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).


Boost allows connecting and disconnecting directly. dysfunctional? 

From boost documentation:

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();

mash.bo...@gmail.com

unread,
May 17, 2015, 5:43:16 PM5/17/15
to std-pr...@isocpp.org, mash.bo...@gmail.com


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. I think it could either be disallowed or detected and redirected to be consumed under the same target type. I don't see it as a problem so great there is no solution.
 

Nicol Bolas

unread,
May 17, 2015, 6:15:19 PM5/17/15
to std-pr...@isocpp.org, mash.bo...@gmail.com
On Sunday, May 17, 2015 at 5:28:39 PM UTC-4, mash.bo...@gmail.com wrote:
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.

OK, you seem to have the way this works backwards.

You are the one suggesting the feature for inclusion in the standard library. The burden of proof as to that feature's utility (and therefore the importance of including it an international standard) is on you.

You can't just say, "I can't imagine it, but I'm sure you'll find some if you search around in random codebases for it." That's your job; as the person proposing the idea, it is your responsibility to find evidence that it is a generally worthwhile feature. And that requires more than just saying, "hey, I use it in event/delegate/signal types."

Equally importantly, you seem to be equating non-member function pointers to your `fun_ptr` class, on the assumption that any code which uses one ought to be able to use the other. I very much cannot agree with that.

If I take an actual non-member function pointer, then I'm taking a purely stateless function. A member pointer+instance is very much not the same thing; it is stateful, since it has that instance pointer there. A member pointer who's instance is part of its parameter list (ie: the return value from `mem_fn`) would be a reasonable equivalent. But once you put an instance pointer there, it stops being an equivalent concept.

How so? Heh heh heh... read on.

Here is a reasonable programming problem for you to write...

There is nothing "reasonable" about this example; it is an obvious strawman example, contrived specifically to make your point. What reason would anyone have for an action executor who's sole purpose is to prevent the execution of the same action twice?

If that is a problem in your code, if you need to explicitly filter someone from executing the same function twice via some arbitrary means, then this suggests that your code is completely out of control. That you have dozens or hundreds of things banging away on some "action" system, and you cannot resolve it in any reasonable way. Therefore, you create an arbitrary and pointless rule about stopping the "same action" from happening multiple times.

That's not to say that it can't happen. But it would be an incredibly rare occurrence. And we don't standardize things to handle rare occurrences.

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);
   
}
}


So... who gets rid of `last_executed`? Oh, that's right; `fun_ptr` isn't supposed to own memory. So you simply have this object that references memory that may or may not exist.

Oh, you may think this sophistry. But it's not. Why? Because it's the source of a gigantic bug in your program. You don't see it? Allow me to explain.

Your `fun_ptr` is stated to be able to take a member pointer+instance as a valid "pointer", correct? OK, let's say you execute some action on an object instance of type T; let's call this instance A. Instance A is allocated on the heap.

You execute that action. So now `last_executed` contains a pointer to the member of T. But it also contains a pointer to A.

Now, let's say we delete A; a perfectly valid thing to do. And then we allocate a new object of type T; call it B. And, since we just deleted A, let's say that our memory manager just so happens to allocate B in the exact same location that A was in.

See the problem?

Now let's execute the same member pointer of T, but on a new instance B. When we try to go through this again, `fun_ptr`'s equality testing system believes that `last_executed` and `action` are the same. That's because both the member pointers and instance pointers are the same. Therefore, our action, which is new by all reasonable definitions of that term... fails to execute.

Why? Because your `fun_ptr` wasn't informed that the memory it originally referenced had been deleted. It thought that it was still a live object, nobody nulled the action out (and since it's a function-static variable, there's no API to do so), and thus, all it contains is a number, which nothing in C++ declares will be unique.

This all happened because `fun_ptr` is blind to ownership, that thing that you seem to believe isn't important. Yet you've managed to prove exactly how essential it is, and therefore exactly how deficient any such `fun_ptr` object would be without it.

Thank you; you did what I could not ;)

So not only is your "problem" obviously contrived and seems unlikely to happen in the real world... you couldn't even solve problem you devised correctly with your own tool.
 
Want to implement this with std::function?

Well, since you have demonstrated that your own `fun_ptr` cannot correctly implement it, I'd say the burden shifts back to you ;)

Nicol Bolas

unread,
May 17, 2015, 6:25:27 PM5/17/15
to std-pr...@isocpp.org, mash.bo...@gmail.com
On Sunday, May 17, 2015 at 5:43:16 PM UTC-4, mash.bo...@gmail.com wrote:
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.

Yes, I know. I said that, when I wrote "but not this..." The problem is that the first is legal. Your newest idea only talked about `const` functors; you said nothing about disallowing member pointers who take non-const `this`.
 
And second I have thought and even pointed out in this thread the ambiguity between those 2 connects.

There is no "ambiguity" between them; there is nothing uncertain about these cases. They are functionally identical. The problem is that your proposal gleefully accepts one while denying the other. This was based on the assumption that shallow, pointer-based equality can be trusted more than deep, stateful equality.

My response to your last post demonstrates how incorrect that assumption is.

mash.bo...@gmail.com

unread,
May 17, 2015, 6:56:33 PM5/17/15
to std-pr...@isocpp.org, mash.bo...@gmail.com
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. 

Nicol Bolas

unread,
May 18, 2015, 1:52:23 AM5/18/15
to std-pr...@isocpp.org, mash.bo...@gmail.com
On Sunday, May 17, 2015 at 6:56:33 PM UTC-4, mash.bo...@gmail.com wrote:
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.

*sigh*

You just brought up the same use case as before. Calling a sequence of unique functions is exactly what a comparison-based event/delegate/signal system does. This is nothing more than a possible implementation of such a system.

So this isn't a new use case; it's the same use case as before.

Also, this "use case" is too low-level. It's not solving a problem; it's an implementation detail for a larger problem.

For example, "I need an object where external code can register/deregister functions, and I can call each of the registered functions in turn." This is a problem statement. One solution to that problem is a comparison-based event/delegate/signal system. And that solution relies upon `fun_ptr` or something like it.

"I have a list of functions and need to ensure that only unique entries exist" isn't a problem statement; it's something you find on a programming test.

Remember: what is in question here is how often people genuinely need to test equality on arbitrary callables. Your "use case" doesn't explain why one has this array of callables or why entries in the array cannot be duplicated. They're just arbitrary conditions, like problems on a test. And without knowing why, we can't gauge whether it is a genuine need (ie: if there might not be other, better alternatives) or how often such a circumstance might arise.

By contrast, "comparison-based event/delegate/signal" system does make it clear why you need the feature. I simply pointed out that a "connection-based event/delegate/signal" system does the same job without the added requirement, as well as offering other benefits.

Michael Boyko

unread,
May 18, 2015, 11:12:04 PM5/18/15
to std-pr...@isocpp.org, mash.bo...@gmail.com
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.
 

Nicol Bolas

unread,
May 19, 2015, 1:04:21 AM5/19/15
to std-pr...@isocpp.org, mash.bo...@gmail.com
On Monday, May 18, 2015 at 11:12:04 PM UTC-4, Michael Boyko wrote:
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.

By your logic, one could say that we should have an algorithm that returns the second item in a sequence. And the use case for it is having a sequence and needing the second item from it.

That's not a use-case; that's begging the question.

"Append unique action" is the same thing, assuming your own conclusion. It's a "problem" which by its own premise, assumes that we're in a situation where where we absolutely need function equality.

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.

And without assuming your own conclusion.

Thus far, the closest you've gotten is your event/delegate/signal system. Which, as boost::signal2::signal::disconnect proves... you don't even `fun_ptr` to implement.

It needs function equality, to be sure. But this happens only as an implementation detail internally; it doesn't expose this to the user (outside of the user having to implement operator==). So the user doesn't have to care if `fun_ptr` has value or reference semantics; they just pass stuff in and everything magically happens with `disconnect`.

Of course, since the connection interface is there, and it works just fine with things that can't compare equal like lambdas, I don't really see the point.
 
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.

Except that Boost.Signal isn't so foolish as to make taking unowned pointers part of its API. The API allows you to define equality for your own functor types, but outside of taking non-member function pointers (which are "owned" by the process), it won't do anything for you. So you are forced to create a member pointer+instance type yourself. Which would allow you to not only define equality as you wish, but also define ownership, rather than assuming naked pointer "ownership".

Also, the bug in question wasn't merely having an unowned pointer. It was having an unowned pointer in a place where the owner of that pointer had no means of clearing it once the pointer was to be destroyed. Though even if you added such a thing, it would still be a hugely bug-prone thing, with objects arbitrarily having to remove themselves from this thing because they've been deleted or something.

And forgetting to do it in one place would yield one of the worst kinds of bug: the kind that don't show up very often.

Which means they'll only show up at 4am when you're trying to ship ;)

Such bugs should be dealt with by making them impossible, not by encouraging their existence.

Michael Boyko

unread,
May 19, 2015, 1:50:18 AM5/19/15
to std-pr...@isocpp.org, mash.bo...@gmail.com


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.

On another note I did implement the equality scheme I posted and all seems to be working as stated. The thing that isn't the greatest characteristic is if someone forgets to define operator== for a functor then comparing two functors of that type results in an unspecified result. If compiler generated equality functions become standard this burden will be somewhat less (but not totally eliminated).

It would also be nice to be able to define operator< and allow all the sorting operations on containers as well. I don't see a path forward on that.

Nicol Bolas

unread,
May 19, 2015, 4:31:02 AM5/19/15
to std-pr...@isocpp.org, mash.bo...@gmail.com
On Tuesday, May 19, 2015 at 1:50:18 AM UTC-4, Michael Boyko wrote:
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.

You again seem to have misunderstood what I said. I'm talking about the problem statement and the way your suggestion fits into the solution.

Let's say I want to propose Boost.Any. And I want to provide evidence that this feature is generally useful. Then I need a problem statement where the best solution to that problem requires the use of Boost.Any. And it needs to not be a contrived problem, nor should it be begging the question..

A justifying problem statement for Boost.Any?

I have an event system, and the events are runtime-bound functions (whether they're derived classes or function pointers is irrelevant). This means that I have a piece of code who's sole purpose it is to pass data between the event signaler and the event handler(s).

OK, fine. Now here's the problem. The caller needs to pass data to the callee. Indeed, every caller needs to pass parameters to any of its callees. My event system should not in any way preclude the use of whatever data the caller and callee want to exchange. How do I go about doing this without having the event signaler #including a bunch of data structures that it doesn't use or rely on?

Clearly, some form of type erasure will be needed. You could use a void*; that's a common practice. And C++ allows it, so long as you cast it back to the exact same type as your originally stored it. Well... what happens when someone casts it to the wrong thing?

Undefined behavior. You'll never know until a mysterious crash stops your program at 4am the night you're trying to ship.

OK, what about a base class with derived implementations? If its a virtual base class, you can use dynamic_cast to ensure that you get the right type (with a hard crash immediately when you get it wrong). Well, that certainly will function as a means of transmitting information. But its a rather ugly solution, as you have all these types who's sole purpose is to be polymorphic. They have nothing in common yet they're in the same class hierarchy.

Not to mention the fact that if I want to pass a simple string or whatever, I have to wrap it in some type. Or, God forbid, some moron uses `static_cast` or even a C-style cast; there's no way to forbid that.

Boost.Any is a more perfect solution to the problem than any of the other alternatives. It's a value type, so any question of ownership is effectively moot. The casts are safe, going to exactly and only the type the caller provided. And the objects you put into them don't have to be related in any way; you can stick anything in there.

That's how it works. You have a problem statement that real programmers experience in real programs (in effect, I just described window message passing). It's high-level enough that you're not begging the question. You list the alternatives, see how they're inadequate, then show how the feature solves the problem better.

At the end of the day, what matters to me is effort vs. gain. Thus far, you have demonstrated very little with regard to gain for this feature. Oh yes, you can compare functions, but until you tell me why I would ever want to do that, it's meaningless. The best case you made for it, an event/delegate/signals system, in no way requires that the user be exposed to such comparison functionality (as proven via Boost.Signal).

Yet you've shown that the effort to standardize this is substantial: you have to define equality for callables. Your best shot at making this palatable is dependent on C++ gaining the ability to define equality for you, which is not a standard feature at the present time.

Boost punted on equality testing for a reason. If you want the committee to make a serious effort to come up with a good resolution for function equality (or at least, take the time to consider yours and the other alternatives), you should come up with some explanation for what benefit this will be.

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.

Anything is "useful" to someone. I'm sure someone out there has had some need for an algorithm who's sole purpose is to return the second item in a sequence. Someone out there would find a remote-controlled umbrella handy.

But things don't get standardized simply because they're "useful" (otherwise we'd have `puts` by now). Sure, sometimes standardizing someone's pet idea gets you STL (though there are still plenty of defects in that). But sometimes, you get std::iostream. And the last time we tried to "make the language more orthogonal and uniform", we got "uniform initialization", which is most assuredly not uniform.

Even your attempt to make callables "more orthogonal and uniform" essentially favors function pointers over functors, since you have to do work to test functor equality. Even if you can auto-generate operator==, it's very easy to lambda capture something that isn't equality testable. Or if you want to ignore certain state during equality testing. Or whatever.

So I lean towards wanting to see evidence. The more difficult what you're proposing is (and yes, it is difficult), and the more potentially dangerous it is (and yes, if you take away non-const functors for `std::function`, that is dangerous), the more evidence I want to see that what we'll get from the effort is actually worthwhile.

Nothing's stopping us from having an event/delegate/signal system right now. So what else you got?

Michael Boyko

unread,
May 28, 2015, 11:39:54 AM5/28/15
to std-pr...@isocpp.org
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).

Patrice Roy

unread,
May 28, 2015, 8:58:50 PM5/28/15
to std-pr...@isocpp.org
First look is nice; I'll look at it in more detail soon to give more complete feedback. Thanks

2015-05-28 11:39 GMT-04:00 Michael Boyko <mboyk...@gmail.com>:
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/.

David Rodríguez Ibeas

unread,
May 29, 2015, 9:05:08 AM5/29/15
to std-pr...@isocpp.org
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?

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));

And multiple other examples could be provided from stateful functors in C++03, probably less and less used now since we have lambdas.

I believe the false positives are a much worse problem than the false negatives as they violate the base principles of value type that you provided.

Equality is complicated. Defining what the *value* of a generalized callable thing is hard.

    David


Michael Boyko

unread,
May 29, 2015, 10:37:12 AM5/29/15
to std-pr...@isocpp.org, dib...@ieee.org
Thank you for taking some time to evaluate this.

On Friday, May 29, 2015 at 8:05:08 AM UTC-5, David Rodríguez Ibeas wrote:
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?

The only salient attribute is the target period. Two fun_ptr's are equal if they have identical targets. While it is true that two consecutive evaluations of the same function could generate different results calling a() or b() at each stage results in the same behavior. that is:

assert(a == b);

if (is_odd(rand())) a();
else b();

if (is_odd(rand())) a();
else b();

will result is the same behavior regardless of the result of rand().

This is the behavior we would expect if a and b were of type void(*)() and it should be the same for the fun_ptr type.
 

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

I think you have a typo here. I think you meant derived::f instead of derived::base. But your point is well taken. That actually will not compile for fun_ptr. I will have to think if that can be supported and still have equality.
 

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));

You are correct. This would evaluate equal when they should not. I missed that lambda captures are not necessarily the same for a given type when they move out of a scope. This probably breaks lambda equality unless the proposal for auto generated operator== is adopted at some point in the future. I'll have to look into this some more. Thanks for the example.
 

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));

Any user defined functor (which mem_fn is) that does not specify an operator== would not have a specified equality as stated in the documentation. In light of your example that breaks lambdas it may be that all function objects would once again be treated the same. That is without operator== the result of fun_ptr's equality is unspecified.

Patrice Roy

unread,
May 29, 2015, 12:19:18 PM5/29/15
to std-pr...@isocpp.org
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?

Michael Boyko

unread,
May 29, 2015, 12:55:14 PM5/29/15
to std-pr...@isocpp.org


On Friday, May 29, 2015 at 11:19:18 AM UTC-5, Patrice Roy wrote:
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 simply erred in thinking type alone is enough to determine equality for non mutating lambda functions - its not as this example shows. My first stab at fun_ptr used reference semantics for function objects which would produce correct results for this example. However it had other issues as pointed out in this thread - mainly creating two fun_ptrs from the same lambda would not be equal as expected. I think it comes down to function objects would have to provide operator== for everything to work correctly. Non capturing lambda's could be handled by way of function pointer conversion but that's only one case.
 

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?

Rejecting duplicates could be one good use for this. Observer pattern is probably the most popular use for such a tool, but other container manipulations needing equality may also be useful.
Reply all
Reply to author
Forward
0 new messages