@@ -56,6 +56,9 @@ namespace mamba::util
5656 { u != t } -> std::convertible_to<bool >;
5757 };
5858
59+ template <class T , class U >
60+ concept weakly_assignable_from = requires (T t, U&& u) { t = std::forward<U>(u); };
61+
5962
6063 // ///////////////////////////
6164
@@ -110,9 +113,8 @@ namespace mamba::util
110113 the mutex types.
111114 */
112115 template <Mutex... M>
113- requires (sizeof ...(M) > 1 )
114- [[nodiscard]]
115- auto lock_as_readonly (M&... mutex)
116+ requires (sizeof ...(M) > 1 ) and (SharedMutex<M> or ...)
117+ [[nodiscard]] auto lock_as_readonly (M&... mutex)
116118 {
117119 return std::make_tuple (lock_as_readonly (mutex)...);
118120 }
@@ -233,11 +235,12 @@ namespace mamba::util
233235
234236 /* * Thread-safe value storage.
235237
236- Holds an object which access is always implying a lock to an associated mutex.
237- The only access to the object without a lock are "unsafe" functions, which are marked as
238- such. Also provides ways to lock the access to the object in scopes. Mainly used when a
239- value needs to be protected by a mutex and we want to make sure the code always does the
240- right locking mechanism.
238+ Holds an object which access is always implying a lock to an associated mutex. The only
239+ access to the object without a lock are "unsafe" functions, which are named as such. Also
240+ provides ways to lock the access to the object for a whole scope.
241+
242+ Mainly used when a value needs to be protected by a mutex and we want to make sure the code
243+ always does the right locking mechanism.
241244
242245 If the mutex type satisfies `SharedMutex`, the locks will be shared if using `const`
243246 functions, enabling cheaper read-only access to the object in that context.
@@ -247,6 +250,8 @@ namespace mamba::util
247250 a bigger scope. `synchronize()` explicitly only builds such scoped-lock and provides it
248251 for scoped usage of the object.
249252
253+ This type is as move-enabled and copy-enabled as it's stored object's type.
254+
250255 Note: this is inspired by boost::thread::synchronized_value and the C++ Concurrent TS 2
251256 paper, refer to these to compare the features and correctness.
252257 */
@@ -261,26 +266,12 @@ namespace mamba::util
261266
262267 synchronized_value () noexcept (std::is_nothrow_default_constructible_v<T>);
263268
264-
265- // NOTE FOR FUTURE DEVS: move operations could be implemented correctly by interpreting it
266- // as moving the stored object after locking `other` first. However,
267- // right now we have no need for this and it might be misleading semantics (we are
268- // not moving the `synchronized_value` per-say, we have moving only it's value).
269- // If the need comes, implement it correctly as a lock followed by a `T` move.
270- // Do not move the `other.m_mutex`, do not `= default;` as these solutions are
271- // incorrect.
272- /* * Moves are forbidden so that the mutex can protect the memory region represented
273- by the stored object.
274-
275- */
276- synchronized_value (synchronized_value&& other) noexcept = delete ;
277- synchronized_value& operator =(synchronized_value&& other) noexcept = delete ;
278-
279269 // / Constructs with a provided value as initializer for the stored object.
280270 template <typename V>
281271 requires (not std::same_as<T, std::decay_t <V>>)
282272 and (not std::same_as<this_type, std::decay_t <V>>)
283- and std::assignable_from<T&, V>
273+ and (not is_type_instance_of_v<std::decay_t <V>, synchronized_value>)
274+ and weakly_assignable_from<T&, V>
284275 synchronized_value (V&& value) noexcept
285276 : m_value(std::forward<V>(value))
286277 {
@@ -302,28 +293,87 @@ namespace mamba::util
302293
303294 /* * Locks the provided `synchronized_value`'s mutex and copies it's stored object value
304295 to this instance's stored object.
305- The lock is released before the end of the call.
306296 If `SharedMutex<M> == true`, the lock is a shared-lock for the provided
307297 `synchronized_value`'s mutex.
298+ The lock is released before the end of the call.
308299 */
309300 synchronized_value (const synchronized_value& other);
310301
311- /* * Locks both mutexes and copies/move the value of the provided `synchronized_value`'s
312- stored object to this instance's stored object. The lock is released before the end of
313- the call. If `SharedMutex<M> == true`, the lock is a shared-lock for the provided
302+ /* * Locks the provided `synchronized_value`'s mutex and moves it's stored object value
303+ into this instance's stored object.
304+ The lock is exclusive.
305+ The lock is released before the end of the call.
306+ */
307+ synchronized_value (synchronized_value&& other) noexcept ;
308+
309+ /* * Locks the provided `synchronized_value`'s mutex and copies it's stored object value
310+ to this instance's stored object.
311+ If `SharedMutex<M> == true`, the lock is a shared-lock for the provided
312+ `synchronized_value`'s mutex.
313+ The lock is released before the end of the call.
314+ */
315+ template <std::default_initializable U, Mutex OtherMutex>
316+ requires (not std::same_as<synchronized_value<T, M>, synchronized_value<U, OtherMutex>>)
317+ and std::constructible_from<T, U>
318+ synchronized_value (const synchronized_value<U, OtherMutex>& other);
319+
320+ /* * Locks the provided `synchronized_value`'s mutex and moves it's stored object value
321+ into this instance's stored object.
322+ The lock is exclusive.
323+ The lock is released before the end of the call.
324+ */
325+ template <std::default_initializable U, Mutex OtherMutex>
326+ requires (not std::same_as<synchronized_value<T, M>, synchronized_value<U, OtherMutex>>)
327+ and std::constructible_from<T, U&&>
328+ synchronized_value (synchronized_value<U, OtherMutex>&& other) noexcept ;
329+
330+ /* * Locks both mutexes and copies the value of the provided `synchronized_value`'s
331+ stored object to this instance's stored object.
332+ If `SharedMutex<M> == true`, the lock is a shared-lock for the provided
333+ `synchronized_value`'s mutex.
334+ The lock is released before the end of the call.
335+ */
336+ auto operator =(const synchronized_value& other) -> synchronized_value&;
337+
338+ /* * Locks both mutexes and moves the value of the provided `synchronized_value`'s
339+ stored object to this instance's stored object.
340+ For both, the lock is exclusive.
341+ The lock is released before the end of the call.
342+ Only available if a `U` instance can be moved into `T` instance.
343+ */
344+ auto operator =(synchronized_value&& other) noexcept -> synchronized_value&;
345+
346+ /* * Locks both mutexes and copies the value of the provided `synchronized_value`'s
347+ stored object to this instance's stored object.
348+ If `SharedMutex<M> == true`, the lock is a shared-lock for the provided
314349 `synchronized_value`'s mutex.
350+ The lock is released before the end of the call.
315351 */
316352 template <std::default_initializable U, Mutex OtherMutex>
317- requires std::assignable_from<T&, U>
353+ requires (not std::same_as<synchronized_value<T, M>, synchronized_value<U, OtherMutex>>)
354+ and weakly_assignable_from<T&, U>
318355 auto operator =(const synchronized_value<U, OtherMutex>& other) -> synchronized_value&;
319356
357+
358+ /* * Locks both mutexes and moves the value of the provided `synchronized_value`'s
359+ stored object to this instance's stored object.
360+ For both, the lock is exclusive.
361+ The lock is released before the end of the call.
362+ Only available if a `U` instance can be moved into `T` instance.
363+ */
364+ template <std::default_initializable U, Mutex OtherMutex>
365+ requires (not std::same_as<synchronized_value<T, M>, synchronized_value<U, OtherMutex>>)
366+ and weakly_assignable_from<T&, U&&>
367+ auto operator =(synchronized_value<U, OtherMutex>&& other) noexcept -> synchronized_value&;
368+
320369 /* * Locks and assign the provided value to the stored object.
321370 The lock is released before the end of the call.
322371 */
323372 template <typename V>
324373 requires (not std::same_as<T, std::decay_t <V>>)
325374 and (not std::same_as<this_type, std::decay_t <V>>)
326- and std::assignable_from<T&, V>
375+ and (not is_type_instance_of_v<std::decay_t <V>, synchronized_value>)
376+ and weakly_assignable_from<T&, V>
327377 auto operator =(V&& value) noexcept -> synchronized_value&
328378 {
329379 // NOTE: when moving the definition outside the class,
@@ -350,8 +400,8 @@ namespace mamba::util
350400 auto value () const -> T;
351401
352402 /* * Locks and return the value of the current object.
353- The lock is released before the end of the call.
354403 If `SharedMutex<M> == true`, the lock is a shared-lock.
404+ The lock is released before the end of the call.
355405 */
356406 [[nodiscard]]
357407 explicit operator T () const
@@ -396,8 +446,8 @@ namespace mamba::util
396446 /* * Locks the mutex and returns a `scoped_locked_ptr` which will provide
397447 non-mutable access to the stored object, while holding the lock for it's whole
398448 lifetime, which usually for this call is for the time of the expression.
399- The lock is released only once the returned object is destroyed.
400449 If `SharedMutex<M> == true`, the lock is a shared-lock.
450+ The lock is released only once the returned object is destroyed.
401451
402452 Example:
403453 synchronized_value<std::vector<int>> values;
@@ -427,8 +477,8 @@ namespace mamba::util
427477 /* * Locks the mutex and returns a `scoped_locked_ptr` which will provide
428478 non-mutable access to the stored object, while holding the lock for it's whole
429479 lifetime.
430- The lock is released only once the returned object is destroyed.
431480 If `SharedMutex<M> == true`, the lock is a shared-lock.
481+ The lock is released only once the returned object is destroyed.
432482
433483 This is mainly used to make sure the stored object doesnt change for a whole scope.
434484 Example:
@@ -468,7 +518,8 @@ namespace mamba::util
468518 /* * Locks the mutex and calls the provided invocable, passing the non-mutable stored object
469519 and the other provided values as arguments.
470520 The lock is released after the provided invocable returns but before this function
471- returns. If `SharedMutex<M> == true`, the lock is a shared-lock.
521+ returns.
522+ If `SharedMutex<M> == true`, the lock is a shared-lock.
472523
473524 This is mainly used to safely execute an already existing function taking the stored
474525 object as parameter. Example:
@@ -519,6 +570,9 @@ namespace mamba::util
519570
520571 T m_value;
521572 mutable M m_mutex;
573+
574+ template <std::default_initializable, Mutex>
575+ friend class synchronized_value ;
522576 };
523577
524578 /* * Locks all the provided `synchronized_value` objects using `.synchronize` and
@@ -560,9 +614,55 @@ namespace mamba::util
560614 m_value = other.m_value ;
561615 }
562616
617+ template <std::default_initializable T, Mutex M>
618+ synchronized_value<T, M>::synchronized_value(synchronized_value&& other) noexcept
619+ {
620+ auto _ = lock_as_exclusive (other.m_mutex );
621+ m_value = std::move (other.m_value );
622+ }
623+
624+ template <std::default_initializable T, Mutex M>
625+ template <std::default_initializable U, Mutex OtherMutex>
626+ requires (not std::same_as<synchronized_value<T, M>, synchronized_value<U, OtherMutex>>)
627+ and std::constructible_from<T, U>
628+ synchronized_value<T, M>::synchronized_value(const synchronized_value<U, OtherMutex>& other)
629+ {
630+ auto _ = lock_as_readonly (other.m_mutex );
631+ m_value = other.m_value ;
632+ }
633+
634+ template <std::default_initializable T, Mutex M>
635+ template <std::default_initializable U, Mutex OtherMutex>
636+ requires (not std::same_as<synchronized_value<T, M>, synchronized_value<U, OtherMutex>>)
637+ and std::constructible_from<T, U&&>
638+ synchronized_value<T, M>::synchronized_value(synchronized_value<U, OtherMutex>&& other) noexcept
639+ {
640+ auto _ = lock_as_exclusive (other.m_mutex );
641+ m_value = std::move (other.m_value );
642+ }
643+
644+ template <std::default_initializable T, Mutex M>
645+ auto synchronized_value<T, M>::operator =(const synchronized_value& other) -> synchronized_value&
646+ {
647+ auto this_lock [[maybe_unused]] = lock_as_exclusive (m_mutex);
648+ auto other_lock [[maybe_unused]] = lock_as_readonly (other.m_mutex );
649+ m_value = other.m_value ;
650+ return *this ;
651+ }
652+
653+ template <std::default_initializable T, Mutex M>
654+ auto synchronized_value<T, M>::operator =(synchronized_value&& other) noexcept
655+ -> synchronized_value&
656+ {
657+ auto _ = lock_as_exclusive (other.m_mutex , m_mutex);
658+ m_value = std::move (other.m_value );
659+ return *this ;
660+ }
661+
563662 template <std::default_initializable T, Mutex M>
564663 template <std::default_initializable U, Mutex OtherMutex>
565- requires std::assignable_from<T&, U>
664+ requires (not std::same_as<synchronized_value<T, M>, synchronized_value<U, OtherMutex>>)
665+ and weakly_assignable_from<T&, U>
566666 auto synchronized_value<T, M>::operator =(const synchronized_value<U, OtherMutex>& other)
567667 -> synchronized_value<T, M>&
568668 {
@@ -572,6 +672,18 @@ namespace mamba::util
572672 return *this ;
573673 }
574674
675+ template <std::default_initializable T, Mutex M>
676+ template <std::default_initializable U, Mutex OtherMutex>
677+ requires (not std::same_as<synchronized_value<T, M>, synchronized_value<U, OtherMutex>>)
678+ and weakly_assignable_from<T&, U&&>
679+ auto synchronized_value<T, M>::operator =(synchronized_value<U, OtherMutex>&& other) noexcept
680+ -> synchronized_value<T, M>&
681+ {
682+ auto _ = lock_as_exclusive (other.m_mutex , m_mutex);
683+ m_value = std::move (other.m_value );
684+ return *this ;
685+ }
686+
575687 template <std::default_initializable T, Mutex M>
576688 auto synchronized_value<T, M>::operator =(const T& value) noexcept -> synchronized_value&
577689 {
0 commit comments