From 5389747f4fff27f85404c96ae969c0c1c7924d76 Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Wed, 20 Apr 2022 11:01:04 +0200 Subject: Add butl::move_only_function[_ex] similar to C++23 std::move_only_function --- libbutl/const-ptr.hxx | 2 - libbutl/move-only-function.hxx | 174 ++++++++++++++++++++++++++++++++++++ libbutl/optional.hxx | 4 +- libbutl/process.cxx | 9 ++ tests/move-only-function/buildfile | 6 ++ tests/move-only-function/driver.cxx | 149 ++++++++++++++++++++++++++++++ 6 files changed, 339 insertions(+), 5 deletions(-) create mode 100644 libbutl/move-only-function.hxx create mode 100644 tests/move-only-function/buildfile create mode 100644 tests/move-only-function/driver.cxx diff --git a/libbutl/const-ptr.hxx b/libbutl/const-ptr.hxx index f0ff706..1474e17 100644 --- a/libbutl/const-ptr.hxx +++ b/libbutl/const-ptr.hxx @@ -5,8 +5,6 @@ #include // nullptr_t -#include - namespace butl { // Const-propagating pointer. diff --git a/libbutl/move-only-function.hxx b/libbutl/move-only-function.hxx new file mode 100644 index 0000000..846ef25 --- /dev/null +++ b/libbutl/move-only-function.hxx @@ -0,0 +1,174 @@ +// file : libbutl/move-only-function.hxx -*- C++ -*- +// license : MIT; see accompanying LICENSE file + +#pragma once + +#include +#include +#include + +namespace butl +{ + // This is a move-only std::function version which is implemented in terms + // of std::function. It is similar to C++23 std::move_only_function but + // still provides target() (but not target_type()). + // + template + class move_only_function_ex; + + // Alias butl::move_only_function to std::move_only_function if available + // and to move_only_function_ex otherwise. + // +#ifdef __cpp_lib_move_only_function + using std::move_only_function; +#else + template + using move_only_function = move_only_function_ex; +#endif + + template + class move_only_function_ex + { + public: + using result_type = R; + + move_only_function_ex () = default; + move_only_function_ex (std::nullptr_t) noexcept {} + + // Note: according to the spec we should also disable these if F is not + // callable, but that is not easy to do in C++14. Maybe we should do + // something for C++17 and later (without this the diagnostics is quite + // hairy). + // + template + move_only_function_ex (F&& f, typename std::enable_if::type, move_only_function_ex>::value>::type* = 0) + { + using FV = typename std::decay::type; + + if (!null (f)) + f_ = wrapper (std::forward (f)); + } + + template + typename std::enable_if::type, move_only_function_ex>::value, move_only_function_ex>::type& + operator= (F&& f) + { + move_only_function_ex (std::forward (f)).swap (*this); + return *this; + } + + move_only_function_ex& + operator= (std::nullptr_t) noexcept + { + f_ = nullptr; + return *this; + } + + void swap (move_only_function_ex& f) noexcept + { + f_.swap (f.f_); + } + + R operator() (A... args) const + { + return f_ (std::forward (args)...); + } + + explicit operator bool () const noexcept + { + return static_cast (f_); + } + + template + T* target() noexcept + { + wrapper* r (f_.template target> ()); + return r != nullptr ? &r->f : nullptr; + } + + template + const T* target() const noexcept + { + const wrapper* r (f_.template target> ()); + return r != nullptr ? &r->f : nullptr; + } + + move_only_function_ex (move_only_function_ex&&) = default; + move_only_function_ex& operator= (move_only_function_ex&&) = default; + + move_only_function_ex (const move_only_function_ex&) = delete; + move_only_function_ex& operator= (const move_only_function_ex&) = delete; + + private: + template + struct wrapper + { + struct empty {}; + + union + { + F f; + empty e; + }; + + explicit wrapper (F&& f_): f (std::move (f_)) {} + explicit wrapper (const F& f_): f (f_) {} + + R operator() (A... args) + { + return f (std::forward (args)...); + } + + R operator() (A... args) const + { + return f (std::forward (args)...); + } + + wrapper (wrapper&& w): f (std::move (w.f)) {} + wrapper& operator= (wrapper&&) = delete; // Shouldn't be needed. + + ~wrapper () {f.~F ();} + + // These shouldn't be called. + // + wrapper (const wrapper&) {} + wrapper& operator= (const wrapper&) {return *this;} + }; + + template static bool null (const F&) {return false;} + template static bool null (R1 (*p) (A1...)) {return p == nullptr;} + template static bool null (const move_only_function_ex& f) {return !f;} + template static bool null (R1 (C::*p) (A1...)) {return p == nullptr;} + template static bool null (R1 (C::*p) (A1...) const) {return p == nullptr;} + + std::function f_; + }; + + template + inline bool + operator== (const move_only_function_ex& f, std::nullptr_t) noexcept + { + return !f; + } + + template + inline bool + operator== (std::nullptr_t, const move_only_function_ex& f) noexcept + { + return !f; + } + + template + inline bool + operator!= (const move_only_function_ex& f, std::nullptr_t) noexcept + { + return static_cast (f); + } + + template + inline bool + operator!= (std::nullptr_t, const move_only_function_ex& f) noexcept + { + return static_cast (f); + } +} diff --git a/libbutl/optional.hxx b/libbutl/optional.hxx index f569f8d..7d66ac5 100644 --- a/libbutl/optional.hxx +++ b/libbutl/optional.hxx @@ -63,9 +63,7 @@ #ifdef LIBBUTL_STD_OPTIONAL namespace butl { - template - using optional = std::optional; - + using std::optional; using std::nullopt_t; using std::nullopt; } diff --git a/libbutl/process.cxx b/libbutl/process.cxx index 5d7710e..5523b96 100644 --- a/libbutl/process.cxx +++ b/libbutl/process.cxx @@ -635,6 +635,10 @@ namespace butl { // Child. // + // NOTE: make sure not to call anything that may acquire a mutex that + // could be already acquired in another thread, most notably + // malloc(). @@ What about exceptions (all the fail() calls)? + // Duplicate the user-supplied (fd > -1) or the created pipe descriptor // to the standard stream descriptor (read end for STDIN_FILENO, write // end otherwise). Close the pipe afterwards. @@ -694,6 +698,9 @@ namespace butl try { + // @@ TODO: redo without allocation (PATH_MAX?) Maybe + // also using C API to avoid exceptions. + // if (e != nullptr) setenv (string (v, e - v), e + 1); else @@ -701,6 +708,8 @@ namespace butl } catch (const system_error& e) { + // @@ Should we assume this cannot throw? + // throw process_child_error (e.code ().value ()); } } diff --git a/tests/move-only-function/buildfile b/tests/move-only-function/buildfile new file mode 100644 index 0000000..9012fd6 --- /dev/null +++ b/tests/move-only-function/buildfile @@ -0,0 +1,6 @@ +# file : tests/move-only-function/buildfile +# license : MIT; see accompanying LICENSE file + +import libs = libbutl%lib{butl} + +exe{driver}: {hxx cxx}{*} $libs diff --git a/tests/move-only-function/driver.cxx b/tests/move-only-function/driver.cxx new file mode 100644 index 0000000..b94d674 --- /dev/null +++ b/tests/move-only-function/driver.cxx @@ -0,0 +1,149 @@ +// file : tests/move-only-function/driver.cxx -*- C++ -*- +// license : MIT; see accompanying LICENSE file + +#include // unique_ptr +#include // move() + +#include + +#undef NDEBUG +#include + +using namespace std; + +static int +func (int v) +{ + return v + 1; +} + +struct functor +{ + int i; + + int + operator() (int v) + { + return v + i; + } +}; + +int +main () +{ + using butl::move_only_function_ex; + + // Attempt to copy-construct or copy-assign should not compile. + // Also check non-collable. + // +#if 0 + { + using ft = move_only_function_ex; + ft f; + ft f2 (f); + ft f3; f3 = f; + ft f4 (123); + } +#endif + + // NULL. + // + { + using ft = move_only_function_ex; + + ft f1; + assert (!f1); + + ft f2 (nullptr); + assert (f2 == nullptr); + + f1 = func; + assert (f1 != nullptr); + f1 = nullptr; + assert (!f1); + + int (*f) (int) = nullptr; + f2 = f; + assert (!f2); + } + + // Function. + // + { + using ft = move_only_function_ex; + + ft f (func); + + assert (f (1) == 2); + + ft f1 (move (f)); + assert (!f); + assert (f1 (1) == 2); + + f = &func; + + assert (f (1) == 2); + + assert (f.target () != nullptr); + assert (f1.target () != nullptr); + } + + // Functor. + // + { + using ft = move_only_function_ex; + + ft f (functor {1}); + + assert (f (1) == 2); + + ft f1 (move (f)); + assert (!f); + assert (f1 (1) == 2); + + f = functor {2}; + + assert (f (1) == 3); + + assert (ft (functor {1}).target () != nullptr); + } + + // Lambda. + // + { + using ft = move_only_function_ex; + + ft f ([p = unique_ptr (new int (1))] (int v) + { + return *p + v; + }); + + assert (f (1) == 2); + + ft f1 (move (f)); + assert (!f); + assert (f1 (1) == 2); + + f = ([p = unique_ptr (new int (2))] (int v) + { + return *p + v; + }); + + assert (f (1) == 3); + } + + // Void result. + // + { + using ft = move_only_function_ex; + + ft f ([] (int v) + { + assert (v == 1); + }); + + f (1); + ft f1 (move (f)); + f1 (1); + } +} -- cgit v1.1