c++templatesmutexlock-guard

Troubles with std::lock_guard<std::mutex> and if constexpr block


There is a class template Foo<T>. And for some specific type, a function should use lock_guard. Here is the example code:

#include <type_traits>
#include <mutex>
#include <vector>

template<typename T> 
class Foo {
public:
    void do_something(int k) {

        if constexpr(std::is_same_v<T, NeedMutexType>) {
            std::lock_guard<std::mutex> lock(mtx_);
        }

        resource_.push_back(k);
        // code for task with resource_ ...
    }

private:
    std::mutex mtx_;
    std::vector<int> resource_;
};

The std::lock_guard will destructed in the end of if constexpr scope. (If it's not true, please correct me.)

To handle this, I can copy the code for task with resource_ below into the if constexpr scope, or just use the raw std::mutex such as mtx_.lock() & mtx_.unlock() instead.

Is there any some ways to handle this with std::lock_guard ? Thanks.


Solution

  • Perhaps std::conditional could come to the rescue here, if you need to do this kind of thing a lot.

    template<class Mutex>
    struct FakeLockGuard { FakeLockGuard(Mutex&){} };
    
    template<typename T, class Mutex = std::mutex>
    using OptionalLock = typename std::conditional<
        std::is_same_v<T, NeedMutexType>,
        std::lock_guard<Mutex>,
        FakeLockGuard<Mutex>>::type;
    

    Here we've defined a do-nothing class template that's constructed the same way as std::lock_guard. We then use that with std::conditional to select either std::lock_guard or FakeLockGuard depending on the result of your type-check.

    Now you can use it as follows:

    template<typename T> 
    class Foo {
    public:
        void do_something(int k)
        {
            OptionalLock<T> lock(mtx_);
            resource_.push_back(k);
            // ...
        }
    
    private:
        std::mutex mtx_;
        std::vector<int> resource_;
    };
    

    You can easily verify this works by setting a breakpoint in the FakeLockGuard constructor or making it output something.

    That's how you can make it all work at compile-time. But I think as you alluded to already, you can simply construct a unique_lock and then conditionally lock it. This has the benefit of being much clearer to whoever has to work with your code. In the end, it's up to whatever you think is most appropriate.