A value_lock type for C++ A value_lock type for C++
Blog
Codex
A value_lock type for C++
Posted June 25, 2026 4 min read Here’s yet another idea I had for a C++ type. It’s a useful tool for simplifying the use of a mutex with a shared resource.<br>Before I show you what I made I’ll demonstrate its purpose with a small demo program. I wrote this to intentionally produce errors due to a multithreaded race condition.& getPair()<br>static pair p{0,0};<br>return p;
int main()<br>vector threads;
for (int i = 0; i 1static pairint,int>& getPair()<br>2{<br>3 static pairint,int> p{0,0};<br>4 return p;<br>5}<br>7int main()<br>8{<br>9 vectorthread> threads;<br>10<br>11 for (int i = 0; i 10; ++i)<br>12 {<br>13 threads.emplace_back([]()<br>14 {<br>15 auto& p = getPair();<br>16 p.first = p.second;<br>17<br>18 // sleep here to coax a context switch in the kernel,<br>19 // making an error more likely<br>20 this_thread::sleep_for(chrono::milliseconds(1));<br>21<br>22 p.second += 1;<br>23 if (p.second - p.first != 1)<br>24 cout "Hey, that's not right!" endl;<br>25 });<br>26 }<br>27<br>28 for (auto& t : threads)<br>29 t.join();<br>30 return 0;<br>31}<br>Every single time I ran this on my computer it resulted in numerous errors, due to the threads interleaving their increments on the shared pair object. Here’s an example of output, where even the console print instructions fell on top of each other.Hey, that's not right!<br>Hey, that's not right!Hey, that's not right!<br>Hey, that's not right!Hey, that's not right!<br>Hey, that's not right!
Hey, that's not right!<br>Hey, that's not right!<br>Hey, that's not right!This is what you would expect to see, it’s a classic example of bad multithreaded code. Here’s how I fixed it using my new value_lock type., mutex > getPair()<br>static pair p{0,0};<br>static mutex mtx;<br>return value_lock{p, mtx};
int main()<br>vector threads;
for (int i = 0; i first = p->second;<br>this_thread::sleep_for(chrono::milliseconds(1));<br>p->second += 1;
if (p.second - p.first != 1)<br>cout 1static value_lock pairint,int>, mutex > getPair()<br>2{<br>3 static pairint,int> p{0,0};<br>4 static mutex mtx;<br>5 return value_lock{p, mtx};<br>6}<br>8int main()<br>9{<br>10 vectorthread> threads;<br>11<br>12 for (int i = 0; i 10; ++i)<br>13 {<br>14 threads.emplace_back([]()<br>15 {<br>16 auto p = getPair();<br>17 p->first = p->second;<br>18 this_thread::sleep_for(chrono::milliseconds(1));<br>19 p->second += 1;<br>20<br>21 if (p.second - p.first != 1)<br>22 cout "Hey, that's not right!" endl;<br>23 });<br>24 }<br>25<br>26 for (auto& t : threads) t.join();<br>27 return 0;<br>28}<br>What gets returned by getPair() holds both a mutex and a reference to the underlying object. On construction the mutex is locked, and on destruction unlocked. In each thread, exclusive access to the underlying reference is guaranteed within the scope of the p variable. This solves the race condition bug in a way that is much more concise and readable than to have used the mutex separately.My new type has another neat trick, which I borrowed the maybe_ptr type I wrote about previously., mutex > getPair()<br>static pair p{0,0};<br>static mutex mtx;<br>return value_lock{p, mtx};
int main()<br>vector threads;
for (int i = 0; i 1static value_lock pairint,int>, mutex > getPair()<br>2{<br>3 static pairint,int> p{0,0};<br>4 static mutex mtx;<br>5 return value_lock{p, mtx};<br>6}<br>8int main()<br>9{<br>10 vectorthread> threads;<br>11<br>12 for (int i = 0; i 10; ++i)<br>13 {<br>14 threads.emplace_back([]()<br>15 {<br>16 for (auto&& p: getPair())<br>17 {<br>18 p.first = p.second;<br>19 this_thread::sleep_for(chrono::milliseconds(1));<br>20 p.second += 1;<br>21<br>22 if (p.second - p.first != 1)<br>23 cout "Hey, that's not right!" endl;<br>24 }<br>25 });<br>26 }<br>27<br>28 for (auto& t : threads) t.join();<br>29 return 0;<br>30}<br>This solves the problem just as before. Using (abusing) the ‘zero or one’ iterator pattern to create a separate scope in which the underlying resource can be accessed exclusively. This use-case ends up being very powerful if you were to use a type of mutex that is non-blocking. It allows you to say concisely, “Do this if the shared resource can be locked, otherwise do nothing”.Here’s the actual implementation for value_lock. class value_lock_iterator<br>public:<br>using iterator_category = std::random_access_iterator_tag;<br>using value_type = std::remove_const_t;<br>using difference_type = std::ptrdiff_t;<br>using pointer = T*;<br>using reference = T&;
constexpr value_lock_iterator(T* p = nullptr) : ptr(p) {}
constexpr reference operator*() const { return *ptr; }<br>constexpr pointer operator->() const { return ptr; }
constexpr value_lock_iterator& operator++() { ptr = nullptr; return *this; }<br>constexpr value_lock_iterator operator++(int) { auto tmp = *this; ++(*this); return tmp; }
constexpr bool operator==(value_lock_iterator const& other) const { return ptr == other.ptr; }<br>constexpr bool operator!=(value_lock_iterator const& other) const { return ptr != other.ptr; }
private:<br>T* ptr;<br>};
template class value_lock<br>public:<br>using iterator = value_lock_iterator;<br>using const_iterator = value_lock_iterator;
explicit value_lock(V& value, M& mtx)<br>: m_value(value), m_lock(mtx)<br>{}
value_lock(value_lock const&) = delete;<br>value_lock&...