aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBoris Kolpackov <boris@codesynthesis.com>2022-04-20 11:01:04 +0200
committerBoris Kolpackov <boris@codesynthesis.com>2022-04-20 11:01:04 +0200
commit5389747f4fff27f85404c96ae969c0c1c7924d76 (patch)
tree1bb521763a40bcfcc069b776eae3d0e95a5f8746
parente9cf760289b47767fdacc6a98fb2040d35a63d49 (diff)
Add butl::move_only_function[_ex] similar to C++23 std::move_only_function
-rw-r--r--libbutl/const-ptr.hxx2
-rw-r--r--libbutl/move-only-function.hxx174
-rw-r--r--libbutl/optional.hxx4
-rw-r--r--libbutl/process.cxx9
-rw-r--r--tests/move-only-function/buildfile6
-rw-r--r--tests/move-only-function/driver.cxx149
6 files changed, 339 insertions, 5 deletions
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 <cstddef> // nullptr_t
-#include <libbutl/export.hxx>
-
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 <utility>
+#include <functional>
+#include <type_traits>
+
+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 <typename>
+ 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 <typename F>
+ using move_only_function = move_only_function_ex<F>;
+#endif
+
+ template <typename R, typename... A>
+ class move_only_function_ex<R (A...)>
+ {
+ 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 <typename F>
+ move_only_function_ex (F&& f, typename std::enable_if<!std::is_same<typename std::remove_reference<F>::type, move_only_function_ex>::value>::type* = 0)
+ {
+ using FV = typename std::decay<F>::type;
+
+ if (!null (f))
+ f_ = wrapper<FV> (std::forward<F> (f));
+ }
+
+ template <typename F>
+ typename std::enable_if<!std::is_same<typename std::remove_reference<F>::type, move_only_function_ex>::value, move_only_function_ex>::type&
+ operator= (F&& f)
+ {
+ move_only_function_ex (std::forward<F> (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<A> (args)...);
+ }
+
+ explicit operator bool () const noexcept
+ {
+ return static_cast<bool> (f_);
+ }
+
+ template <typename T>
+ T* target() noexcept
+ {
+ wrapper<T>* r (f_.template target<wrapper<T>> ());
+ return r != nullptr ? &r->f : nullptr;
+ }
+
+ template <typename T>
+ const T* target() const noexcept
+ {
+ const wrapper<T>* r (f_.template target<wrapper<T>> ());
+ 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 <typename F>
+ 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<A> (args)...);
+ }
+
+ R operator() (A... args) const
+ {
+ return f (std::forward<A> (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 <typename F> static bool null (const F&) {return false;}
+ template <typename R1, typename... A1> static bool null (R1 (*p) (A1...)) {return p == nullptr;}
+ template <typename R1, typename... A1> static bool null (const move_only_function_ex<R1 (A1...)>& f) {return !f;}
+ template <typename R1, typename C, typename... A1> static bool null (R1 (C::*p) (A1...)) {return p == nullptr;}
+ template <typename R1, typename C, typename... A1> static bool null (R1 (C::*p) (A1...) const) {return p == nullptr;}
+
+ std::function<R (A...)> f_;
+ };
+
+ template <typename R, typename... A>
+ inline bool
+ operator== (const move_only_function_ex<R (A...)>& f, std::nullptr_t) noexcept
+ {
+ return !f;
+ }
+
+ template <typename R, typename... A>
+ inline bool
+ operator== (std::nullptr_t, const move_only_function_ex<R (A...)>& f) noexcept
+ {
+ return !f;
+ }
+
+ template <typename R, typename... A>
+ inline bool
+ operator!= (const move_only_function_ex<R (A...)>& f, std::nullptr_t) noexcept
+ {
+ return static_cast<bool> (f);
+ }
+
+ template <typename R, typename... A>
+ inline bool
+ operator!= (std::nullptr_t, const move_only_function_ex<R (A...)>& f) noexcept
+ {
+ return static_cast<bool> (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 <typename T>
- using optional = std::optional<T>;
-
+ 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 <memory> // unique_ptr
+#include <utility> // move()
+
+#include <libbutl/move-only-function.hxx>
+
+#undef NDEBUG
+#include <cassert>
+
+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<int (int)>;
+ ft f;
+ ft f2 (f);
+ ft f3; f3 = f;
+ ft f4 (123);
+ }
+#endif
+
+ // NULL.
+ //
+ {
+ using ft = move_only_function_ex<int (int)>;
+
+ 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<int (int)>;
+
+ 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<int (*) (int)> () != nullptr);
+ assert (f1.target<int (*) (int)> () != nullptr);
+ }
+
+ // Functor.
+ //
+ {
+ using ft = move_only_function_ex<int (int)>;
+
+ 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<functor> () != nullptr);
+ }
+
+ // Lambda.
+ //
+ {
+ using ft = move_only_function_ex<int (int)>;
+
+ ft f ([p = unique_ptr<int> (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<int> (new int (2))] (int v)
+ {
+ return *p + v;
+ });
+
+ assert (f (1) == 3);
+ }
+
+ // Void result.
+ //
+ {
+ using ft = move_only_function_ex<void (int)>;
+
+ ft f ([] (int v)
+ {
+ assert (v == 1);
+ });
+
+ f (1);
+ ft f1 (move (f));
+ f1 (1);
+ }
+}