Here is a quick streamline around type T:
template<class T, class Lock> struct lock_guarded { Lock l; T* t; T* operator->()&&{ return t; } template<class Arg> auto operator[](Arg&&arg)&& -> decltype(std::declval<T&>()[std::declval<Arg>()]) { return (*t)[std::forward<Arg>(arg)]; } T& operator*()&&{ return *t; } }; constexpr struct emplace_t {} emplace {}; template<class T> struct mutex_guarded { lock_guarded<T, std::unique_lock<std::mutex>> get_locked() { return {{m},&t}; } lock_guarded<T const, std::unique_lock<std::mutex>> get_locked() const { return {{m},&t}; } lock_guarded<T, std::unique_lock<std::mutex>> operator->() { return get_locked(); } lock_guarded<T const, std::unique_lock<std::mutex>> operator->() const { return get_locked(); } template<class F> std::result_of_t<F(T&)> operator->*(F&& f) { return std::forward<F>(f)(*get_locked()); } template<class F> std::result_of_t<F(T const&)> operator->*(F&& f) const { return std::forward<F>(f)(*get_locked()); } template<class...Args> mutex_guarded(emplace_t, Args&&...args): t(std::forward<Args>(args)...) {} mutex_guarded(mutex_guarded&& o): t( std::move(*o.get_locked()) ) {} mutex_guarded(mutex_guarded const& o): t( *o.get_locked() ) {} mutex_guarded() = default; ~mutex_guarded() = default; mutex_guarded& operator=(mutex_guarded&& o) { T tmp = std::move(o.get_locked()); *get_locked() = std::move(tmp); return *this; } mutex_guarded& operator=(mutex_guarded const& o): { T tmp = o.get_locked(); *get_locked() = std::move(tmp); return *this; } private: std::mutex m; T t; };
You can use either:
mutex_guarded<std::vector<int>> guarded; auto s0 = guarded->size(); auto s1 = guarded->*[](auto&&e){return e.size();};
both do approximately the same thing, and the guarded object is available only when the mutex is locked.
Stealing from @tsyvarev's answer (with some minor changes):
class shared_recursive_mutex { std::shared_mutex m public: void lock(void) { std::thread::id this_id = std::this_thread::get_id(); if(owner == this_id) { // recursive locking ++count; } else { // normal locking m.lock(); owner = this_id; count = 1; } } void unlock(void) { if(count > 1) { // recursive unlocking count--; } else { // normal unlocking owner = std::thread::id(); count = 0; m.unlock(); } } void lock_shared() { std::thread::id this_id = std::this_thread::get_id(); if (shared_counts->count(this_id)) { ++(shared_count.get_locked()[this_id]); } else { m.lock_shared(); shared_count.get_locked()[this_id] = 1; } } void unlock_shared() { std::thread::id this_id = std::this_thread::get_id(); auto it = shared_count->find(this_id); if (it->second > 1) { --(it->second); } else { shared_count->erase(it); m.unlock_shared(); } } private: std::atomic<std::thread::id> owner; std::atomic<std::size_t> count; mutex_guarded<std::map<std::thread::id, std::size_t>> shared_counts; };
try_lock and try_lock_shared remained as an exercise.
Both lock and unlock the joint lock of the mutex twice (this is safe, because the branches are actually about “this is the mutex control thread”, and the other thread cannot change this answer from “no” to “yes” or vice versa). You can do this with a single lock with ->* instead of -> , which will make it faster (due to some complexity in logic).
The above does not support having an exclusive lock, and then a general lock. It's complicated. It cannot maintain a common lock, and then updates to a unique lock, because in principle it is impossible to stop it from a deadlock when 2 threads try to do this.
This last issue may be the reason why recursive generic mutexes are a bad idea.