Here, we define a public method to add a callback to the list.
I have chosen to require a plain callable as an argument, and then give back
the std::shared_ptr<C> for caller to keep alive while it needs to receive
the events.

The shared_ptr is converted to weak_ptr automatically on push_back call.

public:template<typename...A>voidinvoke(A&&...args){// Go over all callbacks and dispatch on those that are still available.
// Remove all callbacks that are gone.
typenamestd::vector<std::weak_ptr<C>>::iteratoriter;for(iter=this->callbacks.begin();iter!=this->callbacks.end();){autocallback=iter->lock();if(callback){(*callback)(std::forward<A>(args)...);++iter;}else{iter=this->callbacks.erase(iter);}}}

Here, the most interesting part: invoking the event.

In this method we either invoke the callback, or clean it up.
Also we can forward arguments directly to callable using variable argument
template.

Usage

Can be used like this:

{OptimisticDispatcher<function<void(uint32_t,string)>>event;autosubscriber=event.add([](autonumber,autotext){cout<<"Number was "<<number<<" and text was "<<text<<endl;});event.invoke(42,"Hello Universe!");// the subscriber is removed from event here automatically
}

Update

Shortly after writing this blog post, I received this note from Jardic:

Dispatching events usually leads to more events being generated/queued in many program.
And as such, it could lead to modifications of the vector storing the callbacks and invalidating its iterators.

At first I thought that I took care of iterator invalidation by resuming
iteration using the iterator returned from erase:

iter=this->callbacks.erase(iter);

However, I completely missed the case where an event dispatch could lead
into the same dispatcher being updated while it is executing, thus messing up
with callback list.

Looks like it’s best to forego the fancy iterator tricks when dispatching and use plain index instead. That way if something happens to callback list, the index can be appropriately updated and iteration safely restarted.

Also, the erasure of callbacks should happen once, but keeping it in control
requires tracking the recursive invokes, as well as handling the exceptions.

For that, we can store the number of concurrent invokes:

private:...int32_tconcurrent_dispatcher_count=0;

Then, increment and decrement this count for every dispatch iteration:

// Remove all callbacks that are gone, only if we are not dispatching.
if(0==this->concurrent_dispatcher_count){this->callbacks.erase(std::remove_if(this->callbacks.begin(),this->callbacks.end(),[](std::weak_ptr<C>callback){returncallback.expired();}),this->callbacks.end());}

Of course, this dispatcher is still only safe in single-threaded contexts.

Full Code

#include <memory>
#include <vector>
/**
* Dispatches function on a number of callbacks and cleans up callbacks when
* they are dead.
*/template<typenameC>classOptimisticDispatcherfinal{private:std::vector<std::weak_ptr<C>>callbacks;int32_tconcurrent_dispatcher_count=0;public:std::shared_ptr<C>add(C&&callback){autoshared=std::make_shared<C>(callback);this->callbacks.push_back(shared);returnshared;}template<typename...A>voidinvoke(A&&...args){this->concurrent_dispatcher_count++;try{size_tcurrent=0;while(current<this->callbacks.size()){if(autocallback=this->callbacks[current++].lock()){(*callback)(std::forward<A>(args)...);}}this->concurrent_dispatcher_count--;}catch(...){this->concurrent_dispatcher_count--;throw;}// Remove all callbacks that are gone, only if we are not dispatching.
if(0==this->concurrent_dispatcher_count){this->callbacks.erase(std::remove_if(this->callbacks.begin(),this->callbacks.end(),[](std::weak_ptr<C>callback){returncallback.expired();}),this->callbacks.end());}}};

Some Thoughts

C++ let’s us do some things here that would not be possible with generics in
other languages like C#, Java or Rust.

Mainly, we did not need to specify anywhere what kind of callable type is
required by this template.

Also, we were able to easily forward to this unknown type the arbitrary argument
list.

However, the amount of gotchas that were involved in his relatively small
code was sad. Yes, I am C++ novice, but I can’t wait to switch to similarly
powerful language where the compiler can do the safety work. Especially
now, when the Rust has proved it is actually possible.