summaryrefslogtreecommitdiff
path: root/indra/llcommon/llsdutil.h
diff options
context:
space:
mode:
authorNat Goodspeed <nat@lindenlab.com>2022-12-19 16:29:06 -0500
committerNat Goodspeed <nat@lindenlab.com>2023-07-13 12:34:12 -0400
commitc682603417e1ef8290aacf274ff49821bd204c0b (patch)
tree54d245703df0cd6ef1f59b8607b70b2bcd3ebeea /indra/llcommon/llsdutil.h
parent324f0d9b8abad3a74a7c19a6e28f8c77c76b3b83 (diff)
DRTVWR-558: Extend LL::apply() to LLSD array arguments.
Make apply(function, std::array) and apply(function, std::vector) available even when we borrow the C++17 implementation of apply(function, std::tuple). Add apply(function, LLSD) with interpretations: * isUndefined() is treated as an empty array, for calling a nullary function * scalar LLSD is treated as a single-entry array, for calling a unary function * isArray() converts function parameters using LLSDParam * isMap() is an error. Add unit tests for all flavors of LL::apply(). (cherry picked from commit 3006c24251c6259d00df9e0f4f66b8a617e6026d)
Diffstat (limited to 'indra/llcommon/llsdutil.h')
-rw-r--r--indra/llcommon/llsdutil.h81
1 files changed, 80 insertions, 1 deletions
diff --git a/indra/llcommon/llsdutil.h b/indra/llcommon/llsdutil.h
index 372278c51a..eaf8825791 100644
--- a/indra/llcommon/llsdutil.h
+++ b/indra/llcommon/llsdutil.h
@@ -31,6 +31,7 @@
#include "llsd.h"
#include <boost/functional/hash.hpp>
+#include <cassert>
// U32
LL_COMMON_API LLSD ll_sd_from_U32(const U32);
@@ -333,6 +334,31 @@ private:
};
/**
+ * LLSDParam<LLSD> is for when you don't already have the target parameter
+ * type in hand. Instantiate LLSDParam<LLSD>(your LLSD object), and the
+ * templated conversion operator will try to select a more specific LLSDParam
+ * specialization.
+ */
+template <>
+class LLSDParam<LLSD>
+{
+private:
+ LLSD value_;
+
+public:
+ LLSDParam(const LLSD& value): value_(value) {}
+
+ /// if we're literally being asked for an LLSD parameter, avoid infinite
+ /// recursion
+ operator LLSD() const { return value_; }
+
+ /// otherwise, instantiate a more specific LLSDParam<T> to convert; that
+ /// preserves the existing customization mechanism
+ template <typename T>
+ operator T() const { return LLSDParam<T>(value_); }
+};
+
+/**
* Turns out that several target types could accept an LLSD param using any of
* a few different conversions, e.g. LLUUID's constructor can accept LLUUID or
* std::string. Therefore, the compiler can't decide which LLSD conversion
@@ -350,7 +376,7 @@ class LLSDParam<T> \
{ \
public: \
LLSDParam(const LLSD& value): \
- _value((T)value.AS()) \
+ _value((T)value.AS()) \
{} \
\
operator T() const { return _value; } \
@@ -555,4 +581,57 @@ struct hash<LLSD>
}
};
}
+
+namespace LL
+{
+
+/*****************************************************************************
+* apply(function, LLSD array)
+*****************************************************************************/
+// Derived from https://stackoverflow.com/a/20441189
+// and https://en.cppreference.com/w/cpp/utility/apply .
+// We can't simply make a tuple from the LLSD array and then apply() that
+// tuple to the function -- how would make_tuple() deduce the correct
+// parameter type for each entry? We must go directly to the target function.
+template <typename CALLABLE, std::size_t... I>
+auto apply_impl(CALLABLE&& func, const LLSD& array, std::index_sequence<I...>)
+{
+ // call func(unpacked args), using generic LLSDParam<LLSD> to convert each
+ // entry in 'array' to the target parameter type
+ return std::forward<CALLABLE>(func)(LLSDParam<LLSD>(array[I])...);
+}
+
+template <typename CALLABLE>
+auto apply(CALLABLE&& func, const LLSD& args)
+{
+ LLSD array;
+ constexpr auto arity = boost::function_traits<CALLABLE>::arity;
+ // LLSD supports a number of types, two of which are aggregates: Map and
+ // Array. We don't try to support Map: supporting Map would seem to
+ // promise that we could somehow match the string key to 'func's parameter
+ // names. Uh sorry, maybe in some future version of C++ with reflection.
+ assert(! args.isMap());
+ // We expect an LLSD array, but what the heck, treat isUndefined() as a
+ // zero-length array for calling a nullary 'func'.
+ if (args.isUndefined() || args.isArray())
+ {
+ // this works because LLSD().size() == 0
+ assert(args.size() == arity);
+ array = args;
+ }
+ else // args is one of the scalar types
+ {
+ // scalar_LLSD.size() == 0, so don't test that here.
+ // You can pass a scalar LLSD only to a unary 'func'.
+ assert(arity == 1);
+ // make an array of it
+ array = llsd::array(args);
+ }
+ return apply_impl(std::forward<CALLABLE>(func),
+ array,
+ std::make_index_sequence<arity>());
+}
+
+} // namespace LL
+
#endif // LL_LLSDUTIL_H