]> https://gitweb.dealii.org/ - dealii.git/commitdiff
Forward implementation of std::bind_front 15010/head
authorSebastian Proell <sebastian.proell@tum.de>
Fri, 31 Mar 2023 15:47:37 +0000 (17:47 +0200)
committerSebastian Proell <sebastian.proell@tum.de>
Fri, 7 Jul 2023 10:54:53 +0000 (12:54 +0200)
include/deal.II/base/std_cxx20/functional.h [new file with mode: 0644]
tests/base/std_cxx20_functional.cc [new file with mode: 0644]
tests/base/std_cxx20_functional.output [new file with mode: 0644]

diff --git a/include/deal.II/base/std_cxx20/functional.h b/include/deal.II/base/std_cxx20/functional.h
new file mode 100644 (file)
index 0000000..8b0b269
--- /dev/null
@@ -0,0 +1,135 @@
+// ---------------------------------------------------------------------
+//
+// Copyright (C) 2023 by the deal.II authors
+//
+// This file is part of the deal.II library.
+//
+// The deal.II library is free software; you can use it, redistribute
+// it, and/or modify it under the terms of the GNU Lesser General
+// Public License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+// The full text of the license can be found in the file LICENSE.md at
+// the top level directory of deal.II.
+//
+// ---------------------------------------------------------------------
+#ifndef dealii_cxx20_functional_h
+#define dealii_cxx20_functional_h
+
+#include <deal.II/base/config.h>
+
+#ifdef DEAL_II_HAVE_CXX20
+#  include <functional>
+#else
+#  include <functional>
+#  include <tuple>
+#  include <utility>
+#endif
+
+DEAL_II_NAMESPACE_OPEN
+
+namespace std_cxx20
+{
+#ifndef DEAL_II_HAVE_CXX20
+#  ifndef DOXYGEN
+
+  namespace internal
+  {
+    /**
+     * This call is used to invoke the lambda with stored bound arguments.
+     *
+     * @note The extra call is required to pattern-match the index_sequence.
+     */
+    template <typename F,
+              size_t... Ind,
+              typename BoundArgsTuple,
+              typename... CallArgs>
+    constexpr decltype(auto)
+    call_bind(F &&function,
+              std::index_sequence<Ind...>,
+              BoundArgsTuple &&bound_args,
+              CallArgs &&...call_args)
+    {
+      return std::invoke(std::forward<F>(function),
+                         std::get<Ind>(
+                           std::forward<BoundArgsTuple>(bound_args))...,
+                         std::forward<CallArgs>(call_args)...);
+    }
+
+    /**
+     * Return a callable which closely approximates what std::bind_front() is
+     * doing.
+     */
+    template <typename F, typename... BoundArgs>
+    decltype(auto)
+    make_bind_front(F &&f, BoundArgs &&...bound_args)
+    {
+      return [f          = std::forward<F>(f),
+              bound_args = std::make_tuple(
+                std::forward<BoundArgs>(bound_args)...)](auto &&...call_args) {
+        // Perform actual call inside a helper functions which allows to use
+        // pattern-matching to the index sequence.
+        return call_bind(f,
+                         std::index_sequence_for<BoundArgs...>{},
+                         bound_args,
+                         std::forward<decltype(call_args)>(call_args)...);
+      };
+    }
+
+  } // namespace internal
+
+#  endif
+
+  /**
+   * This function generates a forwarding call wrapper to the function @p f
+   * which has its first `n` arguments bound to the `n` arguments passed in
+   * @p bound_args. The implementation is  an approximation to the functionality
+   * provided by `std::bind_front`, see
+   * https://en.cppreference.com/w/cpp/utility/functional/bind_front.
+   *
+   * This function allows to remove boilerplate in user code. Often functions in
+   * the deal.II library take a `std::function` object as an argument. While you
+   * can pass many different types of callable objects to a `std::function`
+   * directly, you cannot pass a pointer to a member function. Instead you can
+   * use the present function to create a compatible callable on-the-fly which
+   * can be assigned to `std::function`. This is demonstrated in the following
+   * example:
+   *
+   * @code
+   *
+   * // An exemplary function that takes an std::function as argument.
+   * void example_function(const std::function<int(double)>& fn);
+   *
+   * class MyClass
+   * {
+   * public:
+   *  void do_something()
+   *  {
+   *    // Pass the member function
+   *    example_function(
+   *      std_cxx20::bind_front(&MyClass::my_function, this));
+   *  }
+   *
+   * private:
+   *  // This function has a signature which is compatible with the function
+   *  // that is expected by example_function().
+   *  int my_function(double);
+   * };
+   *
+   * @endcode
+   */
+  template <typename F, typename... BoundArgs>
+  decltype(auto)
+  bind_front(F &&f, BoundArgs &&...bound_args)
+  {
+    return internal::make_bind_front(std::forward<F>(f),
+                                     std::forward<BoundArgs>(bound_args)...);
+  }
+#else
+  using std::bind_front;
+#endif
+
+} // namespace std_cxx20
+
+DEAL_II_NAMESPACE_CLOSE
+
+#endif
diff --git a/tests/base/std_cxx20_functional.cc b/tests/base/std_cxx20_functional.cc
new file mode 100644 (file)
index 0000000..851adec
--- /dev/null
@@ -0,0 +1,123 @@
+// ---------------------------------------------------------------------
+//
+// Copyright (C) 2023 by the deal.II authors
+//
+// This file is part of the deal.II library.
+//
+// The deal.II library is free software; you can use it, redistribute
+// it, and/or modify it under the terms of the GNU Lesser General
+// Public License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+// The full text of the license can be found in the file LICENSE.md at
+// the top level directory of deal.II.
+//
+// ---------------------------------------------------------------------
+
+#include <deal.II/base/std_cxx20/functional.h>
+
+#include "../tests.h"
+
+
+using namespace dealii;
+
+namespace
+{
+  //! Helper type which is move-only.
+  struct MoveOnly
+  {
+    explicit MoveOnly(int i)
+      : i{i}
+    {}
+
+    MoveOnly(const MoveOnly &)     = delete;
+    MoveOnly(MoveOnly &&) noexcept = default;
+
+    MoveOnly &
+    operator=(const MoveOnly &) = delete;
+    MoveOnly &
+    operator=(MoveOnly &&) = default;
+
+    ~MoveOnly() = default;
+
+    int i;
+  };
+
+
+  // use an interesting signature with move-only types to test that perfect
+  // forwarding works correctly
+  void
+  evaluate(const std::function<int(double, const MoveOnly &, MoveOnly &&)> &fn)
+  {
+    double   d{0.0};
+    MoveOnly m1{1};
+    MoveOnly m2{2};
+    fn(d, m1, std::move(m2));
+  }
+
+  //! Test class which creates callable member functions and passes them to
+  //! evaluate()
+  class Test
+  {
+  public:
+    void
+    run()
+    {
+      auto f = std_cxx20::bind_front(&Test::member, this);
+      f(0.0, MoveOnly{1}, MoveOnly{2});
+      evaluate(std_cxx20::bind_front(&Test::member, this));
+      evaluate(std_cxx20::bind_front(&Test::const_member, this));
+    }
+
+    void
+    run_const() const
+    {
+      evaluate(std_cxx20::bind_front(&Test::const_member, this));
+    }
+
+  private:
+    int
+    member(double d, const MoveOnly &ref, const MoveOnly &mov)
+    {
+      deallog << d << " " << ref.i << " " << mov.i << std::endl;
+      return 0;
+    }
+
+    int
+    const_member(double d, const MoveOnly &ref, MoveOnly &&mov) const
+    {
+      deallog << d << " " << ref.i << " " << mov.i << std::endl;
+      return 0;
+    }
+  };
+
+
+  void
+  f(int a, double b)
+  {
+    deallog << a << " "
+            << " " << b << std::endl;
+  }
+
+  void
+  f2(int a, MoveOnly &&b)
+  {
+    deallog << a << " "
+            << " " << b.i << std::endl;
+  }
+
+} // namespace
+
+int
+main()
+{
+  initlog();
+
+  Test test{};
+  test.run();
+  test.run_const();
+
+  std_cxx20::bind_front(f)(1, 2.0);
+  std_cxx20::bind_front(f2, 1)(MoveOnly{2});
+
+  return 0;
+}
diff --git a/tests/base/std_cxx20_functional.output b/tests/base/std_cxx20_functional.output
new file mode 100644 (file)
index 0000000..0731cbe
--- /dev/null
@@ -0,0 +1,7 @@
+
+DEAL::0.00000 1 2
+DEAL::0.00000 1 2
+DEAL::0.00000 1 2
+DEAL::0.00000 1 2
+DEAL::1  2.00000
+DEAL::1  2

In the beginning the Universe was created. This has made a lot of people very angry and has been widely regarded as a bad move.

Douglas Adams


Typeset in Trocchi and Trocchi Bold Sans Serif.