Skip to content

Instantly share code, notes, and snippets.

@kristopherjohnson
Last active May 30, 2025 19:41
Show Gist options
  • Save kristopherjohnson/f4352bdf35458845bc5deb1265979687 to your computer and use it in GitHub Desktop.
Save kristopherjohnson/f4352bdf35458845bc5deb1265979687 to your computer and use it in GitHub Desktop.
C++ SynchronizedValue<T> class
#ifndef KJ_SYNCHRONIZEDVALUE_H
#define KJ_SYNCHRONIZEDVALUE_H
/*
Copyright 2025 Kristopher Johnson
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the “Software”), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include <functional>
#include <mutex>
#include <type_traits>
#include <utility>
namespace kj {
// Inspired by the C++ proposal for a synchronized_value<T> type
// https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2023/p0290r3.html,
// but compatible with C++11.
/// @brief Wrapper for a value that can be safely accessed from multiple
/// threads.
/// @tparam T Type of the value.
/// @tparam Mutex Type of the mutex to use for synchronization, which must be
/// compatible with `std::lock_guard`.
template <class T, class Mutex = std::mutex> class SynchronizedValue {
mutable Mutex mtx;
T value;
template <class R_, class F_, class T_, class M_>
friend R_ apply(F_ &&f, SynchronizedValue<T_, M_> &sv);
template <class R_, class F_, class T_, class M_>
friend R_ apply(F_ &&f, const SynchronizedValue<T_, M_> &sv);
template <class F_, class T_, class M_>
friend void apply(F_ &&f, SynchronizedValue<T_, M_> &sv);
template <class F_, class T_, class M_>
friend void apply(F_ &&f, const SynchronizedValue<T_, M_> &sv);
template <class F_, class T_, class U_, class M_, class N_>
friend void apply(F_ &&f, SynchronizedValue<T_, M_> &lhs,
SynchronizedValue<U_, N_> &rhs);
public:
using mutex_type = Mutex;
using value_type = T;
/// @brief Default constructor (only available if T is default constructible)
template <class U = T, class = class std::enable_if<
std::is_default_constructible<U>::value>::type>
SynchronizedValue() : value() {
}
/// @brief Constructs a synchronized value with a provided value.
/// @param t Value to be copied or moved into the synchronized value.
SynchronizedValue(const T &t)
: value(t) {
}
/// @brief Constructs a synchronized value with a provided value.
/// @param t Value to be copied or moved into the synchronized value.
SynchronizedValue(T &&t) : value(std::move(t)) {}
~SynchronizedValue() = default;
SynchronizedValue(const SynchronizedValue &) = delete;
SynchronizedValue &operator=(const SynchronizedValue &) = delete;
/// @brief Apply a function to the synchronized value.
/// @param f
void apply(std::function<void(T &)> f) {
std::lock_guard<Mutex> lock(mtx);
f(value);
}
/// @brief Get a copy of the value of the synchronized value.
/// @return A copy of the value.
T get() const {
std::lock_guard<Mutex> lock(mtx);
return value;
}
/// @brief Set the value of the synchronized value from an lvalue reference.
/// @param t New value to set.
void set(const T &t) {
std::lock_guard<Mutex> lock(mtx);
value = t;
}
/// @brief Set the value of the synchronized value from an rvalue reference.
/// @param t New value to set.
void set(T &&t) {
std::lock_guard<Mutex> lock(mtx);
value = std::move(t);
}
/// @brief Exchange values with another SynchronizedValue of the same type.
/// @param other The other instance to swap with.
void swap(SynchronizedValue &other) {
if (this == &other) {
return;
}
std::lock(mtx, other.mtx);
std::lock_guard<Mutex> lock_lhs(mtx, std::adopt_lock);
std::lock_guard<Mutex> lock_rhs(other.mtx, std::adopt_lock);
std::swap(value, other.value);
}
};
/// @brief Applies a function to the value of a SynchronizedValue.
/// @param f Function to apply, which takes a reference to the value type.
/// @param sv Synchronized value to apply the function to.
/// @return The result of the function application.
template <class R, class F, class T, class M>
R apply(F &&f, SynchronizedValue<T, M> &sv) {
std::lock_guard<M> lock(sv.mtx);
return f(sv.value);
}
/// @brief Applies a function to the value of a SynchronizedValue.
/// @param f Function to apply, which takes a reference to the value type.
/// @param sv Synchronized value to apply the function to.
/// @return The result of the function application.
template <class R, class F, class T, class M>
R apply(F &&f, const SynchronizedValue<T, M> &sv) {
std::lock_guard<M> lock(sv.mtx);
return f(sv.value);
}
/// @brief Applies a function to the value of a SynchronizedValue without
/// returning a value.
/// @param f Function to apply, which takes a reference to the value type.
/// @param sv Synchronized value to apply the function to.
/// @return The result of the function application.
template <class F, class T, class M>
void apply(F &&f, SynchronizedValue<T, M> &sv) {
std::lock_guard<M> lock(sv.mtx);
f(sv.value);
}
/// @brief Applies a function to the value of a SynchronizedValue without
/// returning a value.
/// @param f Function to apply, which takes a reference to the value type.
/// @param sv Synchronized value to apply the function to.
/// @return The result of the function application.
template <class F, class T, class M>
void apply(F &&f, const SynchronizedValue<T, M> &sv) {
std::lock_guard<M> lock(sv.mtx);
f(sv.value);
}
/// @brief Apply a function to the values of two synchronized_value objects.
/// @tparam F Function type, which should accept two references to the value
/// type
/// @tparam T Value type of first synchronized_value
/// @tparam U Value type of second synchronized_value
/// @tparam M Mutex type of first synchronized_value
/// @tparam N Mutex type of second synchronized_value
/// @param lhs First synchronized_value
/// @param rhs Second synchronized_value
template <class F, class T, class U, class M, class N>
void apply(F &&f, SynchronizedValue<T, M> &lhs, SynchronizedValue<U, N> &rhs) {
if ((void *)&lhs == (void *)&rhs) {
// same object, so only lock once
std::lock_guard<M> lock(lhs.mtx);
f(lhs.value, rhs.value);
} else {
std::lock(lhs.mtx, rhs.mtx);
std::lock_guard<M> lock_lhs(lhs.mtx, std::adopt_lock);
std::lock_guard<N> lock_rhs(rhs.mtx, std::adopt_lock);
f(lhs.value, rhs.value);
}
}
/// @brief Swaps the values of two synchronized_value objects.
/// @tparam T Value type
/// @tparam M Mutex type of first synchronized_value
/// @tparam N Mutex type of second synchronized value
/// @param lhs First synchronized_value
/// @param rhs Second synchronized_value
template <class T, class M, class N>
void swap(SynchronizedValue<T, M> &lhs, SynchronizedValue<T, N> &rhs) {
if ((void *)&lhs == (void *)&rhs) {
return;
}
apply([](T &a, T &b) { std::swap(a, b); }, lhs, rhs);
}
} // namespace kj
#endif // KJ_SYNCHRONIZEDVALUE_H
@kristopherjohnson
Copy link
Author

kristopherjohnson commented May 30, 2025

Example usage:

#include <iostream>

#include "synchronized_value.h"

using namespace kj;

int main() {
  SynchronizedValue<int> sv(42);

  // Example usage of apply to do something with the value.
  auto doubled = apply<int>([](int &value) { return value * 2; }, sv);
  std::cout << "Result: " << doubled << std::endl;

  // Mutate the value using apply
  apply([](int &value) { value += 10; }, sv);
  std::cout << "Updated Result: " << sv.get() << std::endl;

  // Swap two synchronized values
  SynchronizedValue<std::string> hello("Hello, World!");
  SynchronizedValue<std::string> goodbye("Goodbye, World!");
  swap(hello, goodbye);
  std::cout << "Swapped Values: " << hello.get() << ", " << goodbye.get()
            << std::endl;

  return 0;
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment