Skip to content

Commit

Permalink
Merge pull request #15010 from sebproell/callable-member-function
Browse files Browse the repository at this point in the history
Forward implementation of std::bind_front
  • Loading branch information
drwells committed Jul 7, 2023
2 parents a96e618 + 307e9a3 commit c61d59e
Show file tree
Hide file tree
Showing 3 changed files with 265 additions and 0 deletions.
135 changes: 135 additions & 0 deletions include/deal.II/base/std_cxx20/functional.h
Original file line number Diff line number Diff line change
@@ -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
123 changes: 123 additions & 0 deletions tests/base/std_cxx20_functional.cc
Original file line number Diff line number Diff line change
@@ -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;
}
7 changes: 7 additions & 0 deletions tests/base/std_cxx20_functional.output
Original file line number Diff line number Diff line change
@@ -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

0 comments on commit c61d59e

Please sign in to comment.