Here is a hacky way to add the functionality (untested) using a wrapper class:
/*
this class wraps a ChangeBroadcaster, adding the ability to set lambda callbacks per listener
we inherit from ChangeListener privately to act as a wrapper around a ChangeBroadcaster
held internally, which actually performs the messaging.
We inherit from ChangeBroadcaster publicly, so other ChangeListeners can still listen to
us and use the changeListenerCallback() function if they want.
*/
struct EnhancedChangeBroadcaster : public ChangeBroadcaster, private ChangeListener
{
private:
/* the changeBroadcaster we wrap that actually does the messaging */
ChangeBroadcaster changeBroadcaster;
public:
EnhancedChangeBroadcaster()
{
changeBroadcaster.addChangeListener(this);
}
~EnhancedChangeBroadcaster()
{
changeBroadcaster.removeChangeListener(this);
}
/*
we replicate the ChangeBroadcaster interface for triggering callbacks.
*/
void sendChangeMessage() { changeBroadcaster.sendChangeMessage(); }
void sendSynchronousChangeMessage() { changeBroadcaster.sendSynchronousChangeMessage(); }
void dispatchPendingMessages() { changeBroadcaster.dispatchPendingMessages(); }
private:
struct LambdaChangeListener
{
ChangeListener* changeListener = nullptr;
std::function<void()> func = nullptr;
LambdaChangeListener(ChangeListener* cl, std::function<void()> f) :
changeListener(cl), func(std::move(f)) { }
};
std::vector<LambdaChangeListener> listeners;
/*
here is where we intercept the ChangeBroadcaster callback,
and call the lambda for each changeListener that has been registered with us.
*/
void changeListenerCallback (ChangeBroadcaster* source)
{
for( auto& l : listeners )
{
if( l.changeListener != nullptr )
{
if( l.func )
l.func();
l.changeListener->changeListenerCallback(this);
}
}
}
public:
/*
here is where we replicate the rest of the ChangeBroadcaster interface
*/
void addChangeListener(ChangeListener* l, std::function<void()> func = nullptr)
{
auto existing = std::find_if(listeners.begin(),
listeners.end(),
[l](const LambdaChangeListener& lcl)
{
return lcl.changeListener == l;
});
if( existing == listeners.end() )
{
listeners.emplace_back(l, func);
}
}
Were you going to revise your suggestion? I had something similar already in place but the final behavior isnât the same as my example above. Iâm curious about your thoughts @yfede
Honestly, I withdrew my post because I was not sure it fitted your case. Your reimplementation of a ChangeBroadcaster that is also a ChangeListener got me all confused so I may be missing your point entirely, but my suggestion was:
With a listener class that simply calls a lambda when its change callback is triggered (I see you have a similar class in your code):
class LambdaChangeListener : public ChangeListener
{
std::function <void (ChangeBroadcaster*)> onChange;
void changeListenerCallback (ChangeBroadcaster* source) override
{
if (onChange)
onChange (source);
}
};
And then use as many of those as members (without the need to inherit from them) to define the behavior you desire. For example (I tried to grasp the idea of your initial code):
The implementation that I showed a few posts prior shows how to enable the above idea, but with the ability to remove lambda callbacks and also change the lambda callback for a registered listener.
That is something you canât do with what Iâve shown in this post, as far as I know.
I donât think this is really possible:
It wouldnât be very DRY to pass the same lambda to that addCallback function, and then pass it to removeCallback() later, unless youâre storing it as a variable somewhere. This is the part of C++ Iâm not totally 100% on.
I donât think you can compare std::function objects like this, I think you can only compare them to nullptr. Even if you could I wouldnât expect them to return equal even if theyâre created with the sample lambda as std::function is a fairly complex type erased beast.
There seems to be a bit of conflation between âlambdaâ and âstd::functionâ function here.
You could assign identifiers to them when adding, to remove them later. The ids could be string or int⊠but I personally wouldnât go with that route because it feels like overcomplicating and still not very RAII.
The approach taken by boost::signals2 is to return a Connection object after registering a callback. To sever a connection, you call a detach (I forget the exact name) member function on the Connection. By default, Connections stay attached indefinitely, but they can also be converted to instances of ScopedConnection, which will detach automatically when they go out of scope.
I think itâs fairly unlikely. Too much code relies on the current listener system to change or remove it, and adding a completely separate listener system would be confusing. GUI controls already have both a âListenerListâ and âonChangeâ lambdas, and I donât think adding a third way of achieving the same thing is a good idea.
Iâm not requesting it for GUI controls. Iâm requesting it for ChangeBroadcaster, which is often used to signal that a background thread finished doing some work.
I think this would depend on whatever class youâre using as the Listener to have a standardized interface with regard to adding listeners, and removing them.
Some classes have addListener, others have addChangeListener, others have other types of listener.
You could use some if constexpr (std::is_same<T, Button::Listener>::value) to decide if youâre going to use addListener or if constexpr (std::is_same<T, ChangeListener>::value) to decide if youâre going to use addChangeListener.
Yes, something like that, or the generic implementation of ScopedListener could assume that addListener() and removeListener() are the methods to use, and templace specializations could be provided for deviations to that rule (e.g. ChangeListener)
I think objectToListenTo would need to be some kind of WeakReference, in case the object is deleted before the listener (in which case we just skip the removeListener).
For Components this could be a Component::SafePointer, but for any other it works only, if the ListeneeType implements JUCE_DECLARE_WEAK_REFERENCEABLE, which is not the majority.
Seems it enters a terretory where it creates considerable overhead, but I am not the one to say.
Yeah, I thought about throwing that in, but it would have complicated the example coee.
Weakreference isnât expensive to use. The class adds an if() statements when you try to use operator-> but thatâs really it. What kind of overhead were you referring to?