232 lines
8.1 KiB
C++
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) */
|