H.v.M.
H.v.M.

Reputation: 1659

Why can't I assign an std::optional of std::lock_guard?

Why can't I declare an optional std::lock_guard and then assign it later? The same thing with an optional string works just fine.

This works:

std::mutex m;
std::optional<std::lock_guard<std::mutex>> lg(m);

But this doesn't: (*)

std::mutex m;
std::optional<std::lock_guard<std::mutex>> lg;
lg = std::lock_guard<std::mutex>(m);

And this doesn't: (**)

std::mutex m;
std::optional<std::lock_guard<std::mutex>> lg;
lg = std::optional<std::lock_guard<std::mutex>>(m);

But this does:

std::string s;
std::optional<std::string> os;
os = std::string(s);

And this does:

std::string s;
std::optional<std::string> os;
os = std::optional<std::string>(s);

(*) Error message:

1>c:\dev\repos\tp_iu2\iu2\pretests\j00010_adaptercal.cpp(51): error C2679: binary '=': no operator found which takes a right-hand operand of type 'std::lock_guard<std::mutex>' (or there is no acceptable conversion)
1>c:\program files (x86)\microsoft visual studio\2017\professional\vc\tools\msvc\14.13.26128\include\optional(547): note: could be 'std::optional<std::lock_guard<std::mutex>> &std::optional<std::lock_guard<std::mutex>>::operator =(const std::optional<std::lock_guard<std::mutex>> &)'
1>c:\program files (x86)\microsoft visual studio\2017\professional\vc\tools\msvc\14.13.26128\include\optional(320): note: or       'std::optional<std::lock_guard<std::mutex>> &std::optional<std::lock_guard<std::mutex>>::operator =(std::nullopt_t) noexcept'
1>c:\dev\repos\tp_iu2\iu2\pretests\j00010_adaptercal.cpp(51): note: while trying to match the argument list '(std::optional<std::lock_guard<std::mutex>>, std::lock_guard<std::mutex>)'

(**) Error message:

1>c:\dev\repos\tp_iu2\iu2\pretests\j00010_adaptercal.cpp(51): error C2280: 'std::optional<std::lock_guard<std::mutex>> &std::optional<std::lock_guard<std::mutex>>::operator =(const std::optional<std::lock_guard<std::mutex>> &)': attempting to reference a deleted function
1>c:\program files (x86)\microsoft visual studio\2017\professional\vc\tools\msvc\14.13.26128\include\optional(547): note: compiler has generated 'std::optional<std::lock_guard<std::mutex>>::operator =' here
1>c:\program files (x86)\microsoft visual studio\2017\professional\vc\tools\msvc\14.13.26128\include\optional(547): note: 'std::optional<std::lock_guard<std::mutex>> &std::optional<std::lock_guard<std::mutex>>::operator =(const std::optional<std::lock_guard<std::mutex>> &)': function was implicitly deleted because a base class invokes a deleted or inaccessible function 'std::_Deleted_move_assign<_Base,_Ty> &std::_Deleted_move_assign<_Base,_Ty>::operator =(const std::_Deleted_move_assign<_Base,_Ty> &)'

The solution was to use std::unique_ptr<std::lock_guard<std::mutex>> instead, so I am only asking out of curiosity.

Upvotes: 0

Views: 162

Answers (2)

Toby Speight
Toby Speight

Reputation: 30931

You can copy or move a string into an optional, but mutex locks are neither copyable or moveable. So we can't use the assignment operator.

We could use a std::unique_lock, which is moveable, or we could emplace the lock into the optional:

#include <mutex>
#include <optional>

int main()
{
    std::mutex m;
    std::optional<std::lock_guard<std::mutex>> lg;
    lg.emplace(m);
    lg.reset();
}

I'd advise against this pattern if you can possibly avoid it. Prefer to use lexical scope (perhaps grouping statements into new blocks) to start and end your lock guards if at all possible.

Upvotes: 2

463035818_is_not_an_ai
463035818_is_not_an_ai

Reputation: 123114

std::optional<std::lock_guard<std::mutex>> is not copy assignable because std::lock_guard<std::mutex> is not copy assignable. This is by design. std::lock_guard is not meant to be copied or assigned. Its only purpose is to provide a RAII wrapper that locks a mutex on construction and releases it when the scope is left. If you need no RAII wrapper then std::lock_guard is the wrong wrapper to use. You can either work your way around that or use a type that can be default constructed without a std::mutex and assigned later to lock a mutex:

#include <mutex>

int main() {
    std::mutex mut;
    std::unique_lock<std::mutex> lg;
    // ... later
    lg = std::unique_lock<std::mutex>{mut};
}

Upvotes: 3

Related Questions