Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,43 +6,52 @@

/// \file reflect_action.hpp
/// \brief Reflection-based action definition for HPX remote operations.
///
/// This header provides reflect_action<F>, a C++26 reflection-based
/// replacement for the HPX_PLAIN_ACTION and HPX_REGISTER_ACTION macros.
/// Instead of verbose boilerplate, users write a single line:
///
/// using compute_action = HPX_ACTION(app::compute);
///
/// The action name, function pointer type, arity, and registration are
/// all derived automatically at compile time using C++26 static reflection.

#pragma once

#include <hpx/config.hpp>

#if defined(HPX_HAVE_CXX26_REFLECTION)

#include <hpx/actions_base/basic_action.hpp>
#include <hpx/actions_base/plain_action.hpp>
#include <hpx/serialization/detail/refl_qualified_name_of.hpp>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will have to be

Suggested change
#include <hpx/serialization/detail/refl_qualified_name_of.hpp>
#include <hpx/modules/serialization.hpp>

in order to support C++20 module builds.


#include <cstddef>
#include <meta>
#include <string>
#include <type_traits>

namespace hpx::actions {

/// \cond NOINTERNAL
namespace detail {

/// Helper to extract function type from reflection for use
/// as basic_action template argument (two-step workaround for
/// GCC/Clang restriction on splice expressions as template args).
template <std::meta::info F>
struct reflect_action_base
{
using func_type = [:std::meta::type_of(F):];
using type = basic_action<hpx::actions::detail::plain_function,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This limits the use of reflect_action_base to plain actions? This is most likely fine for now, but will need to be touched upon once you're looking to component actions.

func_type, reflect_action_base<F>>;
};

} // namespace detail
/// \endcond

/// \brief Reflection-based action template.
///
/// reflect_action<F> provides the same interface as HPX_PLAIN_ACTION
/// but derives all properties automatically from the reflected function F:
/// - Qualified name via scope_builder<F>
/// - Function pointer type via std::meta::type_of(F)
/// - Function pointer via splicing [:F:]
/// - Arity via std::meta::parameters_of(F).size()
/// reflect_action<F> integrates with HPX's action system by inheriting
/// from basic_action<detail::plain_function, R(Ps...), reflect_action<F>>.
/// All properties are derived automatically from the reflected function F.
///
/// \tparam F A std::meta::info reflection of a free function.
/// Obtain via the reflection operator: ^^app::my_function
template <std::meta::info F>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be now possible to write:

Suggested change
template <std::meta::info F>
template <typename F>

and reflect on the template argument wherever needed here? This would remove the need for the user to write ^^F on the user-facing API.

struct reflect_action
: basic_action<hpx::actions::detail::plain_function,
typename detail::reflect_action_base<F>::func_type,
reflect_action<F>>
{
/// The function type (e.g. int(double, double))
using func_type = [:std::meta::type_of(F):];
Expand All @@ -57,16 +66,23 @@ namespace hpx::actions {
static constexpr auto name_storage =
hpx::serialization::detail::scope_builder<F>::value;

/// Number of parameters the function takes
static constexpr std::size_t arity = std::meta::parameters_of(F).size();
/// Returns the action name in HPX format: "plain action(app::compute)"
static std::string get_action_name(
naming::address::address_type /*lva*/)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the argument used for? Do we really need it?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, is there a way to avoid returning a std::string (which requires an allocation)?

{
return hpx::actions::detail::make_plain_action_name(
std::string_view(name_storage.data, name_storage.size));
}

/// Returns the fully qualified name of the action.
/// Called by hpx::actions::detail::register_action during
/// static initialization to register this action with the
/// HPX action registry.
static consteval char const* get_action_name() noexcept
/// Invokes the reflected function with the given arguments.
template <typename... Ts>
static auto invoke(naming::address::address_type /*lva*/,
naming::address::component_type /*comptype*/, Ts&&... vs)
{
return name_storage.data;
using base_t = basic_action<hpx::actions::detail::plain_function,
func_type, reflect_action<F>>;
base_t::increment_invocation_count();
return func_ptr(HPX_FORWARD(Ts, vs)...);
}
};

Expand Down
16 changes: 8 additions & 8 deletions libs/full/actions_base/tests/unit/reflect_action_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,29 +47,29 @@ int main()
// Test: action name extraction for simple namespace function
{
HPX_ACTION(app::compute, compute_action);
HPX_TEST_EQ(std::string(compute_action::get_action_name()),
std::string("app::compute"));
HPX_TEST_EQ(std::string(compute_action::get_action_name(nullptr)),
std::string("plain action(app::compute)"));
}

// Test: action name extraction for void noexcept function
{
HPX_ACTION(app::broadcast, broadcast_action);
HPX_TEST_EQ(std::string(broadcast_action::get_action_name()),
std::string("app::broadcast"));
HPX_TEST_EQ(std::string(broadcast_action::get_action_name(nullptr)),
std::string("plain action(app::broadcast)"));
}

// Test: action name extraction for multiple parameters
{
HPX_ACTION(app::transform, transform_action);
HPX_TEST_EQ(std::string(transform_action::get_action_name()),
std::string("app::transform"));
HPX_TEST_EQ(std::string(transform_action::get_action_name(nullptr)),
std::string("plain action(app::transform)"));
}

// Test: action name extraction for nested namespace
{
HPX_ACTION(app::nested::deep_compute, deep_action);
HPX_TEST_EQ(std::string(deep_action::get_action_name()),
std::string("app::nested::deep_compute"));
HPX_TEST_EQ(std::string(deep_action::get_action_name(nullptr)),
std::string("plain action(app::nested::deep_compute)"));
}

// Test: arity extraction
Expand Down
Loading