phoenix-firestorm/indra/llcommon/apply.h

232 lines
8.1 KiB
C++

/**
* @file apply.h
* @author Nat Goodspeed
* @date 2022-06-18
* @brief C++14 version of std::apply()
*
* $LicenseInfo:firstyear=2022&license=viewerlgpl$
* Copyright (c) 2022, Linden Research, Inc.
* $/LicenseInfo$
*/
#if ! defined(LL_APPLY_H)
#define LL_APPLY_H
#include "llexception.h"
#include <boost/type_traits/function_traits.hpp>
#include <functional> // std::mem_fn()
#include <tuple>
#include <type_traits> // std::is_member_pointer
namespace LL
{
/**
* USAGE NOTE:
* https://stackoverflow.com/a/40523474/5533635
*
* If you're trying to pass apply() a variadic function, the compiler
* complains that it can't deduce the callable type, presumably because it
* doesn't know which arity to reify to pass.
*
* But it works to wrap the variadic function in a generic lambda, e.g.:
*
* @CODE
* LL::apply(
* [](auto&&... args)
* {
* return variadic(std::forward<decltype(args)>(args)...);
* },
* args);
* @ENDCODE
*
* Presumably this is because there's only one instance of the generic lambda
* @em type, with a variadic <tt>operator()()</tt>.
*
* It's pointless to provide a wrapper @em function that implicitly supplies
* the generic lambda. You couldn't pass your variadic function to our wrapper
* function, for the same original reason!
*
* Instead we provide a wrapper @em macro. Sorry, Dr. Stroustrup.
*/
#define VAPPLY(FUNC, ARGS) \
LL::apply( \
[](auto&&... args) \
{ \
return (FUNC)(std::forward<decltype(args)>(args)...); \
}, \
(ARGS))
/*****************************************************************************
* invoke()
*****************************************************************************/
#if __cpp_lib_invoke >= 201411L
// C++17 implementation
using std::invoke;
#else // no std::invoke
// Use invoke() to handle pointer-to-method:
// derived from https://stackoverflow.com/a/38288251
template<typename Fn, typename... Args,
typename std::enable_if<std::is_member_pointer<typename std::decay<Fn>::type>::value,
int>::type = 0 >
auto invoke(Fn&& f, Args&&... args)
{
return std::mem_fn(std::forward<Fn>(f))(std::forward<Args>(args)...);
}
template<typename Fn, typename... Args,
typename std::enable_if<!std::is_member_pointer<typename std::decay<Fn>::type>::value,
int>::type = 0 >
auto invoke(Fn&& f, Args&&... args)
{
return std::forward<Fn>(f)(std::forward<Args>(args)...);
}
#endif // no std::invoke
/*****************************************************************************
* apply(function, tuple); apply(function, array)
*****************************************************************************/
#if __cpp_lib_apply >= 201603L
// C++17 implementation
// We don't just say 'using std::apply;' because that template is too general:
// it also picks up the apply(function, vector) case, which we want to handle
// below.
template <typename CALLABLE, typename... ARGS>
auto apply(CALLABLE&& func, const std::tuple<ARGS...>& args)
{
return std::apply(std::forward<CALLABLE>(func), args);
}
#else // C++14
// Derived from https://stackoverflow.com/a/20441189
// and https://en.cppreference.com/w/cpp/utility/apply
template <typename CALLABLE, typename... ARGS, std::size_t... I>
auto apply_impl(CALLABLE&& func, const std::tuple<ARGS...>& args, std::index_sequence<I...>)
{
// We accept const std::tuple& so a caller can construct an tuple on the
// fly. But std::get<I>(const tuple) adds a const qualifier to everything
// it extracts. Get a non-const ref to this tuple so we can extract
// without the extraneous const.
auto& non_const_args{ const_cast<std::tuple<ARGS...>&>(args) };
// call func(unpacked args)
return invoke(std::forward<CALLABLE>(func),
std::forward<ARGS>(std::get<I>(non_const_args))...);
}
template <typename CALLABLE, typename... ARGS>
auto apply(CALLABLE&& func, const std::tuple<ARGS...>& args)
{
// std::index_sequence_for is the magic sauce here, generating an argument
// pack of indexes for each entry in args. apply_impl() can then pass
// those to std::get() to unpack args into individual arguments.
return apply_impl(std::forward<CALLABLE>(func),
args,
std::index_sequence_for<ARGS...>{});
}
#endif // C++14
// per https://stackoverflow.com/a/57510428/5533635
template <typename CALLABLE, typename T, size_t SIZE>
auto apply(CALLABLE&& func, const std::array<T, SIZE>& args)
{
return apply(std::forward<CALLABLE>(func), std::tuple_cat(args));
}
/*****************************************************************************
* bind_front()
*****************************************************************************/
// To invoke a non-static member function with a tuple, you need a callable
// that binds your member function with an instance pointer or reference.
// std::bind_front() is perfect: std::bind_front(&cls::method, instance).
// Unfortunately bind_front() only enters the standard library in C++20.
#if __cpp_lib_bind_front >= 201907L
// C++20 implementation
using std::bind_front;
#else // no std::bind_front()
template<typename Fn, typename... Args,
typename std::enable_if<!std::is_member_pointer<typename std::decay<Fn>::type>::value,
int>::type = 0 >
auto bind_front(Fn&& f, Args&&... args)
{
// Don't use perfect forwarding for f or args: we must bind them for later.
return [f, pfx_args=std::make_tuple(args...)]
(auto&&... sfx_args)
{
// Use perfect forwarding for sfx_args because we use them as soon as
// we receive them.
return apply(
f,
std::tuple_cat(pfx_args,
std::make_tuple(std::forward<decltype(sfx_args)>(sfx_args)...)));
};
}
template<typename Fn, typename... Args,
typename std::enable_if<std::is_member_pointer<typename std::decay<Fn>::type>::value,
int>::type = 0 >
auto bind_front(Fn&& f, Args&&... args)
{
return bind_front(std::mem_fn(std::forward<Fn>(f)), std::forward<Args>(args)...);
}
#endif // C++20 with std::bind_front()
/*****************************************************************************
* apply(function, std::vector)
*****************************************************************************/
// per https://stackoverflow.com/a/28411055/5533635
template <typename CALLABLE, typename T, std::size_t... I>
auto apply_impl(CALLABLE&& func, const std::vector<T>& args, std::index_sequence<I...>)
{
return apply(std::forward<CALLABLE>(func),
std::make_tuple(args[I]...));
}
// produce suitable error if apply(func, vector) is the wrong size for func()
void apply_validate_size(size_t size, size_t arity);
/// possible exception from apply() validation
struct apply_error: public LLException
{
apply_error(const std::string& what): LLException(what) {}
};
template <size_t ARITY, typename CALLABLE, typename T>
auto apply_n(CALLABLE&& func, const std::vector<T>& args)
{
apply_validate_size(args.size(), ARITY);
return apply_impl(std::forward<CALLABLE>(func),
args,
std::make_index_sequence<ARITY>());
}
/**
* apply(function, std::vector) goes beyond C++17 std::apply(). For this case
* @a function @emph cannot be variadic: the compiler must know at compile
* time how many arguments to pass. This isn't Python. (But see apply_n() to
* pass a specific number of args to a variadic function.)
*/
template <typename CALLABLE, typename T>
auto apply(CALLABLE&& func, const std::vector<T>& args)
{
// infer arity from the definition of func
constexpr auto arity = boost::function_traits<CALLABLE>::arity;
// now that we have a compile-time arity, apply_n() works
return apply_n<arity>(std::forward<CALLABLE>(func), args);
}
} // namespace LL
#endif /* ! defined(LL_APPLY_H) */