Skip to content

Commit

Permalink
What is a particle? (#280)
Browse files Browse the repository at this point in the history
Meta-programming magic to better define the particle concept and
constraints (with tests!).

Related to #279.

Signed-off-by: Nahuel Espinosa <[email protected]>
  • Loading branch information
nahueespinosa authored Jan 6, 2024
1 parent c329bba commit 1e922e8
Show file tree
Hide file tree
Showing 11 changed files with 571 additions and 122 deletions.
32 changes: 2 additions & 30 deletions beluga/include/beluga/mixin/utility.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
#include <type_traits>
#include <variant>

#include <beluga/utility/forward_like.hpp>

/**
* \file
* \brief Implementation of extensions to the standard utility library.
Expand Down Expand Up @@ -130,36 +132,6 @@ struct is_reference_wrapper {
template <typename T>
inline constexpr bool is_reference_wrapper_v = is_reference_wrapper<T>::value; // NOLINT

/// Returns a reference to a value which has similar properties to `T&&`.
/**
* Implementation taken from https://en.cppreference.com/w/cpp/utility/forward_like
* since this feature is only available starting with C++23.
*
* The program is ill-formed if `T&&` is not a valid type.
*
* \tparam T The type from which to take the properties.
* \tparam U The type of the input value.
* \param value A value that needs to be forwarded like type `T`.
* \return A reference to value of the determined type.
*/
template <class T, class U>
[[nodiscard]] constexpr auto&& forward_like(U&& value) noexcept {
constexpr bool is_adding_const = std::is_const_v<std::remove_reference_t<T>>; // NOLINT
if constexpr (std::is_lvalue_reference_v<T&&>) {
if constexpr (is_adding_const) {
return std::as_const(value);
} else {
return static_cast<U&>(value);
}
} else {
if constexpr (is_adding_const) {
return std::move(std::as_const(value));
} else {
return std::forward<U>(value);
}
}
}

/// Helper function to unwrap a reference_wrapper or forward the given value.
/**
* \tparam T The type of the input value.
Expand Down
231 changes: 194 additions & 37 deletions beluga/include/beluga/primitives.hpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2023 Ekumen, Inc.
// Copyright 2023-2024 Ekumen, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand All @@ -20,6 +20,7 @@

#include <beluga/type_traits/strongly_typed_numeric.hpp>
#include <beluga/type_traits/tuple_traits.hpp>
#include <beluga/utility/forward_like.hpp>

/**
* \file
Expand Down Expand Up @@ -61,33 +62,62 @@ namespace state_detail {

/// \cond state_detail

template <class T, class = void>
struct has_member_variable_state : std::false_type {};

template <class T>
struct has_member_variable_state<T, std::void_t<decltype(std::declval<T>().state)>> : std::true_type {};

template <class T>
inline constexpr bool has_member_variable_state_v = has_member_variable_state<T>::value;

template <class T, class = void>
struct has_member_state : std::false_type {};

template <class T>
struct has_member_state<T, std::void_t<decltype(std::declval<T>().state())>> : std::true_type {};

template <typename T>
template <class T>
inline constexpr bool has_member_state_v = has_member_state<T>::value;

/// \endcond
template <class T, class = void>
struct has_non_member_state : std::false_type {};

/// Default state implementation.
/**
* Assumes that the particle is a tuple and the first element is the state.
*/
template <typename T>
constexpr decltype(auto) state(T&& t) noexcept(noexcept(std::get<0>(std::forward<T>(t)))) {
return std::get<0>(std::forward<T>(t));
}
template <class T>
struct has_non_member_state<T, std::void_t<decltype(state(std::declval<T>()))>> : std::true_type {};

template <class T>
inline constexpr bool has_non_member_state_v = has_non_member_state<T>::value;

/// \endcond

/// Customization point object type for accessing the `state` of a particle.
/**
* See https://en.cppreference.com/w/cpp/ranges/cpo.
*/
struct state_fn {
/// Overload for when the particle type defines a member variable.
template <
class T,
std::enable_if_t<
std::conjunction_v<
has_member_variable_state<T>, //
std::negation<has_member_state<T>>, //
std::negation<has_non_member_state<T>>>,
int> = 0>
constexpr decltype(auto) operator()(T&& t) const noexcept {
return beluga::forward_like<T>(t.state);
}

/// Overload for when the particle type defines a member method.
template <typename T, std::enable_if_t<has_member_state_v<T>, int> = 0>
template <
class T,
std::enable_if_t<
std::conjunction_v<
std::negation<has_member_variable_state<T>>, //
has_member_state<T>, //
std::negation<has_non_member_state<T>>>,
int> = 0>
constexpr decltype(auto) operator()(T&& t) const noexcept(noexcept(std::forward<T>(t).state())) {
return std::forward<T>(t).state();
}
Expand All @@ -96,10 +126,35 @@ struct state_fn {
/**
* The non-member function must be in T's namespace so it can be found by ADL.
*/
template <typename T, std::enable_if_t<!has_member_state_v<T>, int> = 0>
template <
class T,
std::enable_if_t<
std::conjunction_v<
std::negation<has_member_variable_state<T>>, //
std::negation<has_member_state<T>>, //
has_non_member_state<T>>,
int> = 0>
constexpr decltype(auto) operator()(T&& t) const noexcept(noexcept(state(std::forward<T>(t)))) {
return state(std::forward<T>(t));
}

/// Overload for tuple-like types.
/**
* Assumes that the first element is the state.
*/
template <
class T,
std::enable_if_t<
std::conjunction_v<
std::negation<has_member_variable_state<T>>, //
std::negation<has_member_state<T>>, //
std::negation<has_non_member_state<T>>, //
is_tuple_like<T>>,
int> = 0,
std::enable_if_t<(std::tuple_size_v<std::decay_t<T>> > 1), int> = 0>
constexpr decltype(auto) operator()(T&& t) const noexcept(noexcept(std::get<0>(std::forward<T>(t)))) {
return std::get<0>(std::forward<T>(t));
}
};

} // namespace state_detail
Expand All @@ -111,33 +166,62 @@ namespace weight_detail {

/// \cond weight_detail

template <class T, class = void>
struct has_member_variable_weight : std::false_type {};

template <class T>
struct has_member_variable_weight<T, std::void_t<decltype(std::declval<T>().weight)>> : std::true_type {};

template <class T>
inline constexpr bool has_member_variable_weight_v = has_member_variable_weight<T>::value;

template <class T, class = void>
struct has_member_weight : std::false_type {};

template <class T>
struct has_member_weight<T, std::void_t<decltype(std::declval<T>().weight())>> : std::true_type {};

template <typename T>
template <class T>
inline constexpr bool has_member_weight_v = has_member_weight<T>::value;

/// \endcond
template <class T, class = void>
struct has_non_member_weight : std::false_type {};

/// Default weight implementation.
/**
* Assumes that the particle is a tuple that has exactly one element of type `beluga::Weight`.
*/
template <typename T>
constexpr decltype(auto) weight(T&& t) noexcept(noexcept(element_of_type<beluga::Weight>(std::forward<T>(t)))) {
return element_of_type<beluga::Weight>(std::forward<T>(t));
}
template <class T>
struct has_non_member_weight<T, std::void_t<decltype(weight(std::declval<T>()))>> : std::true_type {};

template <class T>
inline constexpr bool has_non_member_weight_v = has_non_member_weight<T>::value;

/// \endcond

/// Customization point object type for accessing the `weight` of a particle.
/**
* See https://en.cppreference.com/w/cpp/ranges/cpo.
*/
struct weight_fn {
/// Overload for when the particle type defines a member variable.
template <
class T,
std::enable_if_t<
std::conjunction_v<
has_member_variable_weight<T>, //
std::negation<has_member_weight<T>>, //
std::negation<has_non_member_weight<T>>>,
int> = 0>
constexpr decltype(auto) operator()(T&& t) const noexcept {
return beluga::forward_like<T>(t.weight);
}

/// Overload for when the particle type defines a member method.
template <typename T, std::enable_if_t<has_member_weight_v<T>, int> = 0>
template <
class T,
std::enable_if_t<
std::conjunction_v<
std::negation<has_member_variable_weight<T>>, //
has_member_weight<T>, //
std::negation<has_non_member_weight<T>>>,
int> = 0>
constexpr decltype(auto) operator()(T&& t) const noexcept(noexcept(std::forward<T>(t).weight())) {
return std::forward<T>(t).weight();
}
Expand All @@ -146,10 +230,32 @@ struct weight_fn {
/**
* The non-member function must be in T's namespace so it can be found by ADL.
*/
template <typename T, std::enable_if_t<!has_member_weight_v<T>, int> = 0>
template <
class T,
std::enable_if_t<
std::conjunction_v<
std::negation<has_member_variable_weight<T>>, //
std::negation<has_member_weight<T>>, //
has_non_member_weight<T>>,
int> = 0>
constexpr decltype(auto) operator()(T&& t) const noexcept(noexcept(weight(std::forward<T>(t)))) {
return weight(std::forward<T>(t));
}

/// Overload for tuple-like types.
template <
class T,
std::enable_if_t<
std::conjunction_v<
std::negation<has_member_variable_weight<T>>, //
std::negation<has_member_weight<T>>, //
std::negation<has_non_member_weight<T>>, //
is_tuple_like<T>, //
has_single_element<beluga::Weight, std::decay_t<T>>>,
int> = 0>
constexpr decltype(auto) operator()(T&& t) const noexcept(noexcept(element<beluga::Weight>(std::forward<T>(t)))) {
return element<beluga::Weight>(std::forward<T>(t));
}
};

} // namespace weight_detail
Expand All @@ -161,33 +267,62 @@ namespace cluster_detail {

/// \cond cluster_detail

template <class T, class = void>
struct has_member_variable_cluster : std::false_type {};

template <class T>
struct has_member_variable_cluster<T, std::void_t<decltype(std::declval<T>().cluster)>> : std::true_type {};

template <class T>
inline constexpr bool has_member_variable_cluster_v = has_member_variable_cluster<T>::value;

template <class T, class = void>
struct has_member_cluster : std::false_type {};

template <class T>
struct has_member_cluster<T, std::void_t<decltype(std::declval<T>().cluster())>> : std::true_type {};

template <typename T>
template <class T>
inline constexpr bool has_member_cluster_v = has_member_cluster<T>::value;

/// \endcond
template <class T, class = void>
struct has_non_member_cluster : std::false_type {};

/// Default cluster implementation.
/**
* Assumes that the particle is a tuple that has exactly one element of type `beluga::Cluster`.
*/
template <typename T>
constexpr decltype(auto) cluster(T&& t) noexcept(noexcept(element_of_type<beluga::Cluster>(std::forward<T>(t)))) {
return element_of_type<beluga::Cluster>(std::forward<T>(t));
}
template <class T>
struct has_non_member_cluster<T, std::void_t<decltype(cluster(std::declval<T>()))>> : std::true_type {};

template <class T>
inline constexpr bool has_non_member_cluster_v = has_non_member_cluster<T>::value;

/// \endcond

/// Customization point object type for accessing the `cluster` of a particle.
/**
* See https://en.cppreference.com/w/cpp/ranges/cpo.
*/
struct cluster_fn {
/// Overload for when the particle type defines a member variable.
template <
class T,
std::enable_if_t<
std::conjunction_v<
has_member_variable_cluster<T>, //
std::negation<has_member_cluster<T>>, //
std::negation<has_non_member_cluster<T>>>,
int> = 0>
constexpr decltype(auto) operator()(T&& t) const noexcept {
return beluga::forward_like<T>(t.cluster);
}

/// Overload for when the particle type defines a member method.
template <typename T, std::enable_if_t<has_member_cluster_v<T>, int> = 0>
template <
class T,
std::enable_if_t<
std::conjunction_v<
std::negation<has_member_variable_cluster<T>>, //
has_member_cluster<T>, //
std::negation<has_non_member_cluster<T>>>,
int> = 0>
constexpr decltype(auto) operator()(T&& t) const noexcept(noexcept(std::forward<T>(t).cluster())) {
return std::forward<T>(t).cluster();
}
Expand All @@ -196,10 +331,32 @@ struct cluster_fn {
/**
* The non-member function must be in T's namespace so it can be found by ADL.
*/
template <typename T, std::enable_if_t<!has_member_cluster_v<T>, int> = 0>
template <
class T,
std::enable_if_t<
std::conjunction_v<
std::negation<has_member_variable_cluster<T>>, //
std::negation<has_member_cluster<T>>, //
has_non_member_cluster<T>>,
int> = 0>
constexpr decltype(auto) operator()(T&& t) const noexcept(noexcept(cluster(std::forward<T>(t)))) {
return cluster(std::forward<T>(t));
}

/// Overload for tuple-like types.
template <
class T,
std::enable_if_t<
std::conjunction_v<
std::negation<has_member_variable_cluster<T>>, //
std::negation<has_member_cluster<T>>, //
std::negation<has_non_member_cluster<T>>, //
is_tuple_like<T>, //
has_single_element<beluga::Cluster, std::decay_t<T>>>,
int> = 0>
constexpr decltype(auto) operator()(T&& t) const noexcept(noexcept(element<beluga::Cluster>(std::forward<T>(t)))) {
return element<beluga::Cluster>(std::forward<T>(t));
}
};

} // namespace cluster_detail
Expand Down
Loading

0 comments on commit 1e922e8

Please sign in to comment.