Skip to content

Instantly share code, notes, and snippets.

@saxbophone
Last active March 6, 2025 23:43
Show Gist options
  • Save saxbophone/d7e71ea0bd60ea4443c8ad7bd05e1a92 to your computer and use it in GitHub Desktop.
Save saxbophone/d7e71ea0bd60ea4443c8ad7bd05e1a92 to your computer and use it in GitHub Desktop.
Reloaded version of my Seizable™ concept
#include <mutex> // locks
#include <shared_mutex> // shared_mutex
#include <utility> // exposing structured-binding support for SynchroniseAll
template <typename T>
class Synchronised {
public:
using Mutex = std::shared_mutex;
using ReadOnlyLock = std::shared_lock<Mutex>;
using ReadWriteLock = std::unique_lock<Mutex>;
class Watched {
public:
constexpr const T& at() const { return *_watched; }
constexpr const T& operator*() const { return at(); }
constexpr const T* operator->() const { return _watched; }
private:
constexpr Watched(const T& data, Mutex& mutex)
: _watched{&data}
, _lock{mutex}
{}
const T* _watched;
ReadOnlyLock _lock;
friend Synchronised;
};
class Seized {
public:
constexpr const T& at() const { return *_seized; }
constexpr T& at() { return *_seized; }
constexpr const T& operator*() const { return at(); }
constexpr T& operator*() { return at(); }
constexpr const T* operator->() const { return _seized; }
constexpr T* operator->() { return _seized; }
private:
constexpr Seized(T& data, Mutex& mutex)
: _seized{&data}
, _lock{mutex}
{}
T* _seized;
ReadWriteLock _lock;
friend Synchronised;
};
constexpr Synchronised() = default;
constexpr Synchronised(const T& value) : _data{value} {}
constexpr T operator=(const T& value) { return set(value); }
constexpr T set(const T& value) {
ReadWriteLock write_lock{_mutex};
_data = value;
return _data;
}
constexpr operator T() const { return get(); }
constexpr T get() const {
ReadOnlyLock read_lock{_mutex};
return _data;
}
// NOTE: Since Synchronised<> provides atomic operations only, we don't
// expose any operations that return pointer or reference.
// Use .watch() or .seize() for that.
constexpr Watched watch() { return {_data, _mutex}; }
constexpr Seized seize() { return {_data, _mutex}; }
private:
T _data;
mutable Mutex _mutex;
template <typename... Ts> // TODO: refactor if possible to avoid this friend declaration
friend class SynchroniseAll;
};
template <typename... Ts>
class SynchroniseAll {
public:
constexpr SynchroniseAll(Synchronised<Ts>&... synchroniseds)
: _data{synchroniseds._data...} // NOTE: Relies upon friendship in order to access private members
, _lock{synchroniseds._mutex...}
{}
using TupleOf = std::tuple<Ts&...>;
template <size_t I>
constexpr std::tuple_element<I, SynchroniseAll>::type& get() { return std::get<I>(_data); }
private:
TupleOf _data;
std::scoped_lock<typename Synchronised<Ts>::Mutex...> _lock;
};
// TODO: See if there's a way to put these nested inside SynchroniseAll class...
template <typename... Ts>
struct std::tuple_size<SynchroniseAll<Ts...>> {
static constexpr size_t value = std::tuple_size<typename SynchroniseAll<Ts...>::TupleOf>::value;
};
template <size_t I, typename... Ts>
struct std::tuple_element<I, SynchroniseAll<Ts...>> : std::tuple_element<I, typename SynchroniseAll<Ts...>::TupleOf>
{};
struct GarkWunkle {
int blaim;
float dreebs_per_bloom;
unsigned vinder_nunkin;
};
int main() {
Synchronised<int> foo = 43;
Synchronised<int> bar = -991;
Synchronised<float> baz = 42.19975f;
{
auto [foo_val, bar_val, baz_val] = SynchroniseAll(foo, bar, baz);
foo_val += bar_val - baz_val;
}
Synchronised<GarkWunkle> tally_bunger{{123, -45.46f, 110u}};
tally_bunger.seize()->blaim -= 42;
return tally_bunger.get().blaim;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment