How to return std::lock_guard in a std::pair

  • A+
Category:Languages

When I go to return a std::lock_guard in a std::pair from a function I get horrible errors. But when I package it in a class I have no problems (compiles and works as expected). I can't understand why. Details follow:

I devised a small template class to enable convenient locking and unlocking of shared objects. It's not particularly innovative, but C++17 allows it to be very compact and code read/write friendly:

template <typename T> class Locked { public:     Locked(T& _object, std::mutex& _mutex)         : object(_object)         , lock(_mutex)     {     }      T&                          object;     std::lock_guard<std::mutex> lock; };  template <typename T> class Lockable { public:     Locked<T>       borrow() { return Locked(object, mutex); }     Locked<const T> borrow() const { return Locked(object, mutex); }  private:     T                  object;     mutable std::mutex mutex; }; 

It can be used like:

int main() {     Lockable<std::vector<int>> lv;      auto [vec, lock] = lv.borrow();      std::cout << vec.size() << std::endl; } 

My question is this. The Locked class is very thin. I thought I could use std::pair instead of a formal class, like so:

#include <iostream> #include <mutex> #include <utility> #include <vector>  template <typename T> class Lockable { public:     std::pair<T&, std::lock_guard<std::mutex>>       borrow()     { return std::pair(object, std::lock_guard<std::mutex>(mutex)); }     std::pair<const T&, std::lock_guard<std::mutex>> borrow() const     { return std::pair(object, std::lock_guard<std::mutex>(mutex)); }  private:     T                  object;     mutable std::mutex mutex; };  int main() {     Lockable<std::vector<int>> lv;      auto [vec, lock] = lv.borrow();      std::cout << vec.size() << std::endl; } 

But this throws up horrible, hard to parse errors. I think this has to do with std::lock_guard not being movable, but to me, it looks exactly like my working code. Why does this second version not work?

 


With some massaging Lockable does compile:

template <typename T> class Lockable { public:     auto borrow()       { return std::pair<      T&, std::lock_guard<std::mutex>>{object, mutex}; }     auto borrow() const { return std::pair<const T&, std::lock_guard<std::mutex>>{object, mutex}; }  private:     T                  object;     mutable std::mutex mutex; }; 

Live example.

The idea is to explicitly specify std::lock_guard as a template argument in std::pair, but feed mutex as the corresponding constructor argument (indeed, the second version doesn't work because std::lock_guard isn't movable). Overload (3) of std::pair::pair will be used in this case.

(Also, since it is C++17, I would suggest to use std::scoped_lock instead of std::lock_guard).

Comment

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: