In C++17, guaranteed copy elision means that there isn't necessarily a constructor call when a local variable is initialized by a function call that returns a scoped_lockable by value. That causes code such as:
// see test for definition of ReadLockedPtr ReadLockedPtr<X> get(); void f() { auto my_ptr = get(); my_ptr->do_something_requiring_lock(); }
... to fail for two reasons: the lock my_ptr->mutex is not held in the do_something_requiring_lock() call, and the lock my_ptr is not held at the point of the implicit destructor call at the end of the function.
There isn't really a good way to work around this: we don't have a list of "these are the locks you should expect to be held when an object is returned by value". But we can approximate that by pretending that the local my_ptr variable was actually initialized by running the move constructor of ReadLockedPtr, which is what this patch does.
[Note that this doesn't address the more problematic case where the returned type has no copy or move constructor. If that comes up, it can be hacked around with something like:
struct SCOPED_LOCKABLE MyImmovableButReturnableScopedLock { MyImmovableButReturnableScopedLock(MyImmovableButReturnableScopedLock &&) = delete; MyImmovableButReturnableScopedLock(volatile MyImmovableButReturnableScopedLock&&) ATTRIBUTES_HERE; // not defined };
... on the basis that no sane program will ever actually call the other move constructor.]
Ideas for alternative approaches welcome. Ideally I'd like something that allows existing C++<=14 code to continue to work in C++17 mode unchanged, which I think this approach mostly provides.
Can you sprinkle some const correctness around this declaration?