// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- // vim: ts=8 sw=2 smarttab /* * Ceph - scalable distributed file system * * Copyright (C) 2004-2006 Sage Weil * * This is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License version 2.1, as published by the Free Software * Foundation. See file COPYING. * */ #include #include #include #include #include #include #include #include "common/shunique_lock.h" #include "include/assert.h" // I despise you. Not you the reader, I'm talking // to the include file. #ifndef CEPH_COMMON_CONVENIENCE_H #define CEPH_COMMON_CONVENIENCE_H namespace ceph { // Lock Factories // ============== // // I used to, whenever I declared a mutex member variable of a class, // declare a pile of types like: // ```cpp // using unique_lock = ::std::unique_lock; // ``` // to avoid having to type that big, long type at every use. It also // let me change the mutex type later. It's inelegant and breaks down // if you have more than one type of mutex in the same class. So here // are some lock factories. template inline auto uniquely_lock(Mutex&& m, Args&& ...args) -> std::unique_lock > { return std::unique_lock >( std::forward(m), std::forward(args)... ); } template inline auto sharingly_lock(Mutex&& m, Args&& ...args) -> std::shared_lock > { return std::shared_lock >( std::forward(m), std::forward(args)...); } template inline auto shuniquely_lock(std::unique_lock&& m, Args&& ...args) -> shunique_lock > { return shunique_lock >( std::forward >(m), std::forward(args)...); } template inline auto shuniquely_lock(std::shared_lock&& m, Args&& ...args) -> shunique_lock > { return shunique_lock >( std::forward >(m), std::forward(args)...); } template inline auto shuniquely_lock(Mutex&& m, Args&& ...args) -> shunique_lock > { return shunique_lock >( std::forward(m), std::forward(args)...); } // All right! These two don't work like the others. You cannot do // `auto l = guardedly_lock(m)` since copy elision before C++17 is // optional. C++17 makes it mandatory so these workarounds won't be // needed. // // To use this, you'll need to do something like: // `auto&& l = guardedly_lock(m)` // This way, we aren't actually copying or moving a value, we're // binding a reference to a temporary which extends its lifetime until // the end of the enclosing block. // // You may in fact want to use // `[[gnu::unused]] auto&& l = guardedly_lock(m)` // To avoid the unused variable warning. Since reference assignment // doesn't have side effects, normally, this just looks to the // compiler (whose static analysis is not the sharpest hammer in the // drawer) like a reference we bind for no reason and never // use. Perhaps future compilers will be smarter about this, but since // they'll also implement C++17 people might not care. // template inline auto guardedly_lock(Mutex&& m) -> std::lock_guard > { m.lock(); // So the way this works is that Copy List Initialization creates // one and only one Temporary. There is no implicit copy that is // generally optimized away the way there is if we were to just try // something like `return std::lock_guard(m)`. // // The function then returns this temporary as a prvalue. We cannot // bind it to a variable, because that would implicitly copy it // (even if in practice RVO would mean there is no copy), so instead // the user can bind it to a reference. (It has to be either a const // lvalue reference or an rvalue reference.) // // So we get something analogous to all the others with a mildly // wonky syntax. The need to use [[gnu::unused]] is honestly the // worst part. It makes this construction unfortunately rather // long. return { std::forward(m), std::adopt_lock }; } template inline auto guardedly_lock(Mutex&& m, std::adopt_lock_t) -> std::lock_guard > { return { std::forward(m), std::adopt_lock }; } template inline auto with_unique_lock(Mutex&& mutex, Fun&& fun, Args&&... args) -> decltype(fun(std::forward(args)...)) { // Yes I know there's a lock guard inside and not a unique lock, but // the caller doesn't need to know or care about the internal // details, and the semantics are those of unique locking. [[gnu::unused]] auto&& l = guardedly_lock(std::forward(mutex)); return std::forward(fun)(std::forward(args)...); } template inline auto with_shared_lock(Mutex&& mutex, Fun&& fun, Args&&... args) -> decltype(fun(std::forward(args)...)) { auto l = sharingly_lock(std::forward(mutex)); return std::forward(fun)(std::forward(args)...); } } // Lock Types // ---------- // // Lock factories are nice, but you still have to type out a huge, // obnoxious template type when declaring a function that takes or // returns a lock class. // #define UNIQUE_LOCK_T(m) \ ::std::unique_lock> #define SHARED_LOCK_T(m) \ ::std::shared_lock> #define SHUNIQUE_LOCK_T(m) \ ::ceph::shunique_lock> namespace ceph { // boost::optional is wonderful! Unfortunately it lacks a function for // the thing you would most obviously want to do with it: apply a // function to its contents. // There are two obvious candidates. The first is a function that // takes a function and an optional value and returns an optional // value, either holding the return value of the function or holding // nothing. // // I'd considered making more overloads for mutable lvalue // references, but those are going a bit beyond likely use cases. // template auto maybe_do(const boost::optional& t, F&& f) -> boost::optional)>> { if (t) return { std::forward(f)(*t) }; else return boost::none; } // The other obvious function takes an optional but returns an // ‘unwrapped’ value, either the result of evaluating the function or // a provided alternate value. // template auto maybe_do_or(const boost::optional& t, F&& f, U&& u) -> std::result_of_t)> { static_assert(std::is_convertible_v>, "Alternate value must be convertible to function return type."); if (t) return std::forward(f)(*t); else return std::forward(u); } // Same thing but for std::optional template auto maybe_do(const std::optional& t, F&& f) -> std::optional)>> { if (t) return { std::forward(f)(*t) }; else return std::nullopt; } // The other obvious function takes an optional but returns an // ‘unwrapped’ value, either the result of evaluating the function or // a provided alternate value. // template auto maybe_do_or(const std::optional& t, F&& f, U&& u) -> std::result_of_t)> { static_assert(std::is_convertible_v>, "Alternate value must be convertible to function return type."); if (t) return std::forward(f)(*t); else return std::forward(u); } namespace _convenience { template inline void for_each_helper(const std::tuple& t, const F& f, std::index_sequence) { (f(std::get(t)), ..., void()); } template inline void for_each_helper(std::tuple& t, const F& f, std::index_sequence) { (f(std::get(t)), ..., void()); } template inline void for_each_helper(const std::tuple& t, F& f, std::index_sequence) { (f(std::get(t)), ..., void()); } template inline void for_each_helper(std::tuple& t, F& f, std::index_sequence) { (f(std::get(t)), ..., void()); } } template inline void for_each(const std::tuple& t, const F& f) { _convenience::for_each_helper(t, f, std::index_sequence_for{}); } template inline void for_each(std::tuple& t, const F& f) { _convenience::for_each_helper(t, f, std::index_sequence_for{}); } template inline void for_each(const std::tuple& t, F& f) { _convenience::for_each_helper(t, f, std::index_sequence_for{}); } template inline void for_each(std::tuple& t, F& f) { _convenience::for_each_helper(t, f, std::index_sequence_for{}); } } #endif // CEPH_COMMON_CONVENIENCE_H