Jan Rüegg Jan Rüegg -4 years ago 53
C++ Question

Have two different versions of arrow operator?

I have the following class for locking objects:

#include <memory>

template <class Type, class Mutex>
class LockableObject {
public:
class UnlockedObject {
public:
UnlockedObject(Mutex &mutex, Type &object)
: mutex_(mutex), object_(object) {}
UnlockedObject(UnlockedObject &&other) = default;

// No copying allowed
UnlockedObject(const UnlockedObject &) = delete;
UnlockedObject &operator=(const UnlockedObject &) = delete;

~UnlockedObject() { mutex_.unlock(); }

Type *operator->() { return &object_; } // Version 1
// Type &operator->() { return object_; } // Version 2


private:
Mutex &mutex_;
Type &object_;
};

template <typename... Args>
LockableObject(Args &&... args) : object_(std::forward<Args>(args)...) {}

UnlockedObject Lock() {
mutex_.lock();
return UnlockedObject(mutex_, object_);
}

private:
Mutex mutex_;
Type object_;
};


I would like to use it as follows to lock and unlock access to a shared object. The second example makes use of the
->
operators's ability to apply itself multiple times:

// Example 1
{
LockableObject<std::string, std::mutex> locked_string;
auto unlocked_string = locked_string.Lock();
// This is what I want:
unlocked_string->size(); // works for version 1, breaks for version 2
}

// Example 2
{
LockableObject<std::unique_ptr<std::string>, std::mutex> locked_string(std::unique_ptr<std::string>(new std::string()));
auto unlocked_string = locked_string.Lock();
// This is what I want:
unlocked_string->size(); // works for version 2, breaks for Version 1

// Workaround
unlocked_string->get()->size(); // works for version 1, but is not nice
}


Can the class somehow be changed to have both examples use
unlocked_string->size()
instead of the workaround with
->get()
? Possibly by using template specialisation or something similar?

Answer Source

Thanks for the hints and answers. I ended up using the following to detect presence of the arrow operator:

template <class> struct type_sink { typedef void type; };  // consumes a type, and makes it `void`
template <class T> using type_sink_t = typename type_sink<T>::type;
template <class T, class = void> struct has_arrow : std::false_type {};
template <class T> struct has_arrow<T, type_sink_t<decltype(std::declval<T>().operator->())> > : std::true_type {};

And then conditionally enabling or disabling the two versions of the functions like this:

template <class CopyType = Type>
typename std::enable_if<!has_arrow<CopyType>::value, CopyType *>::type
operator->() { return &object_; }  // Version 1

template <class CopyType = Type>
typename std::enable_if<has_arrow<CopyType>::value, CopyType &>::type
operator->() { return object_; }  // Version 2
Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download