From 307e9a3713754712b2f54f80569c5a1bf40ac0e1 Mon Sep 17 00:00:00 2001 From: Sebastian Proell Date: Fri, 31 Mar 2023 17:47:37 +0200 Subject: [PATCH] Forward implementation of std::bind_front --- include/deal.II/base/std_cxx20/functional.h | 135 ++++++++++++++++++++ tests/base/std_cxx20_functional.cc | 123 ++++++++++++++++++ tests/base/std_cxx20_functional.output | 7 + 3 files changed, 265 insertions(+) create mode 100644 include/deal.II/base/std_cxx20/functional.h create mode 100644 tests/base/std_cxx20_functional.cc create mode 100644 tests/base/std_cxx20_functional.output diff --git a/include/deal.II/base/std_cxx20/functional.h b/include/deal.II/base/std_cxx20/functional.h new file mode 100644 index 0000000000..8b0b269fb4 --- /dev/null +++ b/include/deal.II/base/std_cxx20/functional.h @@ -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 + +#ifdef DEAL_II_HAVE_CXX20 +# include +#else +# include +# include +# include +#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 + constexpr decltype(auto) + call_bind(F &&function, + std::index_sequence, + BoundArgsTuple &&bound_args, + CallArgs &&...call_args) + { + return std::invoke(std::forward(function), + std::get( + std::forward(bound_args))..., + std::forward(call_args)...); + } + + /** + * Return a callable which closely approximates what std::bind_front() is + * doing. + */ + template + decltype(auto) + make_bind_front(F &&f, BoundArgs &&...bound_args) + { + return [f = std::forward(f), + bound_args = std::make_tuple( + std::forward(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{}, + bound_args, + std::forward(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& 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 + decltype(auto) + bind_front(F &&f, BoundArgs &&...bound_args) + { + return internal::make_bind_front(std::forward(f), + std::forward(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 index 0000000000..851adec216 --- /dev/null +++ b/tests/base/std_cxx20_functional.cc @@ -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 + +#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 &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 index 0000000000..0731cbe298 --- /dev/null +++ b/tests/base/std_cxx20_functional.output @@ -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 -- 2.39.5