Skip to content
Snippets Groups Projects

feat(functors) implement automatic wrapping/unwrapping of optional returns from functor

Merged Christoph Hasse requested to merge chasse_fixed_for_362 into master
1 file
+ 86
17
Compare changes
  • Side-by-side
  • Inline
@@ -13,8 +13,10 @@
#include "Functors/Optional.h"
#include "GaudiKernel/detected.h"
#include "SelKernel/Utilities.h"
#include "boost/mp11/algorithm.hpp"
#include "boost/mp11/bind.hpp"
#include <cmath>
#include <type_traits>
namespace Functors::detail {
template <typename T>
@@ -185,19 +187,53 @@ namespace Functors {
tuple_of_inputs );
}
template <std::size_t NO, std::size_t Idx, typename F, typename M, typename... In>
auto bind_helper( F const& tup, M mask, In&&... inputs ) {
if constexpr ( NO == Idx ) {
return std::tuple{};
} else {
template <std::size_t... Idx, typename F, typename M, typename... In>
auto bind_helper_optional( std::index_sequence<Idx...>, F const& tup, M mask, In&&... inputs ) {
auto tuple_of_results = std::tuple( std::get<Idx + 1>( tup )( mask_arg, mask, inputs... )... );
// FIXME avoid copy here. auto_cref
// maybe -> https://www.youtube.com/watch?v=XXsZBZjzS-E??
auto const& ret = std::get<Idx>( tup )( mask_arg, mask, inputs... );
auto check = []( auto const& val ) {
if constexpr ( is_our_optional_v<std::decay_t<decltype( val )>> ) { return val.has_value(); }
if constexpr ( is_tuple_v<decltype( ret )> ) { return ret; }
return std::tuple{std::move( ret )};
}
return true;
};
// check if any of the contained optionals do not have a value;
bool abort = !( check( std::get<Idx>( tuple_of_results ) ) && ... );
// FIXME can we avoid copies in this mess?
auto get_val = []( auto&& val ) {
using val_t = decltype( val );
if constexpr ( is_our_optional_v<std::decay_t<val_t>> ) {
return std::tuple{val.value()};
} else if constexpr ( detail::is_tuple_v<val_t> ) {
return val;
} else {
return std::tuple{val};
}
};
// use std::move on the tuple_of_results so that the get_val sees a
// rvalue reference and can move the objects
return abort
? std::nullopt
: Functors::Optional( std::tuple_cat( get_val( std::get<Idx>( std::move( tuple_of_results ) ) )... ) );
}
template <std::size_t... Idx, typename F, typename M, typename... In>
auto bind_helper( std::index_sequence<Idx...>, F const& tup, M mask, In&&... inputs ) {
// FIXME there are definitely a few copies here that could be avoided.
// investigate the possibility to use a multi_apply as described in:
// https://stackoverflow.com/a/58545993
auto as_tuple = []( auto&& val ) -> decltype( auto ) {
using val_t = decltype( val );
if constexpr ( is_tuple_v<std::decay_t<val_t>> ) {
return std::forward<val_t>( val );
} else {
return std::tuple{std::forward<decltype( val )>( val )};
}
};
return std::tuple_cat( as_tuple( std::get<Idx + 1>( tup )( mask_arg, mask, inputs... ) )... );
}
/** @class ComposedFunctor
@@ -309,11 +345,44 @@ namespace Functors {
} else if constexpr ( std::is_same_v<Operator, composition::bind> ) {
auto const& main_functor = std::get<0>( fs );
return std::apply(
[mask, &main_functor]( auto&&... args ) -> decltype( auto ) {
return main_functor( mask_arg, mask, args... );
},
std::tuple_cat( bind_helper<0, I>( fs, mask, input... )... ) );
// a couple lines of boost mp11 template magic to check if any of
// the bound functors return an optional.
using types_of_functors = std::remove_cv_t<decltype( fs )>;
using types_of_bound_functors = boost::mp11::mp_pop_front<types_of_functors>;
// we want to check every functors reulst type when called with
// (mask, input...) so we bind those to the std::invoke_result_t
// call and then invoke that function with every functor
using func =
boost::mp11::mp_bind_back<std::invoke_result_t, mask_arg_t, decltype( mask ), decltype( input )...>;
using result_types = boost::mp11::mp_transform_q<func, types_of_bound_functors>;
constexpr bool has_optional_return =
boost::mp11::mp_to_bool<boost::mp11::mp_count_if<result_types, is_our_optional_t>>::value;
// pass status variable into bind_helper
if constexpr ( has_optional_return ) {
// tuple { val, tuple<values...>, optional}
auto bound_functor_results =
bind_helper_optional( std::make_index_sequence<sizeof...( I ) - 1>{}, fs, mask, input... );
return !bound_functor_results.has_value()
? std::nullopt
: Functors::Optional{std::apply(
[mask, &main_functor]( auto&&... args ) -> decltype( auto ) {
return main_functor( mask_arg, mask, args... );
},
bound_functor_results.value() )};
} else {
// no functor returns optional values, so we can safely enter the
// fast path :)
return std::apply(
[mask, &main_functor]( auto&&... args ) -> decltype( auto ) {
return main_functor( mask_arg, mask, args... );
},
bind_helper( std::make_index_sequence<sizeof...( I ) - 1>{}, fs, mask, input... ) );
}
} else {
// Evaluate all the functors and let the given Operator combine the
// results.
Loading