c++c++17mutexstdoptionallock-guard

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.


Solution

  • 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.