summaryrefslogtreecommitdiff
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
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)
-rw-r--r--indra/llcommon/CMakeLists.txt2
-rw-r--r--indra/llcommon/apply.h23
-rw-r--r--indra/llcommon/llsdutil.h81
-rw-r--r--indra/llcommon/tests/apply_test.cpp171
4 files changed, 269 insertions, 8 deletions
diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt
index 91ae2440fa..941d2d7baf 100644
--- a/indra/llcommon/CMakeLists.txt
+++ b/indra/llcommon/CMakeLists.txt
@@ -293,6 +293,8 @@ if (LL_TESTS)
#set(TEST_DEBUG on)
set(test_libs llcommon)
+ LL_ADD_INTEGRATION_TEST(apply "" "${test_libs}")
+ LL_ADD_INTEGRATION_TEST(bitpack "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(classic_callback "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(commonmisc "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(lazyeventapi "" "${test_libs}")
diff --git a/indra/llcommon/apply.h b/indra/llcommon/apply.h
index 7c58d63bc0..26753f5017 100644
--- a/indra/llcommon/apply.h
+++ b/indra/llcommon/apply.h
@@ -13,6 +13,7 @@
#define LL_APPLY_H
#include <boost/type_traits/function_traits.hpp>
+#include <cassert>
#include <tuple>
namespace LL
@@ -54,6 +55,9 @@ namespace LL
}, \
(ARGS))
+/*****************************************************************************
+* apply(function, tuple)
+*****************************************************************************/
#if __cplusplus >= 201703L
// C++17 implementation
@@ -63,8 +67,8 @@ using std::apply;
// Derived from https://stackoverflow.com/a/20441189
// and https://en.cppreference.com/w/cpp/utility/apply
-template <typename CALLABLE, typename TUPLE, std::size_t... I>
-auto apply_impl(CALLABLE&& func, TUPLE&& args, std::index_sequence<I...>)
+template <typename CALLABLE, typename... ARGS, std::size_t... I>
+auto apply_impl(CALLABLE&& func, const std::tuple<ARGS...>& args, std::index_sequence<I...>)
{
// call func(unpacked args)
return std::forward<CALLABLE>(func)(std::move(std::get<I>(args))...);
@@ -81,6 +85,11 @@ auto apply(CALLABLE&& func, const std::tuple<ARGS...>& args)
std::index_sequence_for<ARGS...>{});
}
+#endif // C++14
+
+/*****************************************************************************
+* apply(function, std::array)
+*****************************************************************************/
// 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)
@@ -88,13 +97,15 @@ auto apply(CALLABLE&& func, const std::array<T, SIZE>& args)
return apply(std::forward<CALLABLE>(func), std::tuple_cat(args));
}
+/*****************************************************************************
+* 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_impl(std::forward<CALLABLE>(func),
- std::make_tuple(std::forward<T>(args[I])...),
- I...);
+ return apply(std::forward<CALLABLE>(func),
+ std::make_tuple(args[I]...));
}
// this goes beyond C++17 std::apply()
@@ -108,8 +119,6 @@ auto apply(CALLABLE&& func, const std::vector<T>& args)
std::make_index_sequence<arity>());
}
-#endif // C++14
-
} // namespace LL
#endif /* ! defined(LL_APPLY_H) */
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
diff --git a/indra/llcommon/tests/apply_test.cpp b/indra/llcommon/tests/apply_test.cpp
new file mode 100644
index 0000000000..1f1085a702
--- /dev/null
+++ b/indra/llcommon/tests/apply_test.cpp
@@ -0,0 +1,171 @@
+/**
+ * @file apply_test.cpp
+ * @author Nat Goodspeed
+ * @date 2022-12-19
+ * @brief Test for apply.
+ *
+ * $LicenseInfo:firstyear=2022&license=viewerlgpl$
+ * Copyright (c) 2022, Linden Research, Inc.
+ * $/LicenseInfo$
+ */
+
+// Precompiled header
+#include "linden_common.h"
+// associated header
+#include "apply.h"
+// STL headers
+// std headers
+// external library headers
+// other Linden headers
+#include "../test/lltut.h"
+#include "llsd.h"
+#include "llsdutil.h"
+
+/*****************************************************************************
+* TUT
+*****************************************************************************/
+namespace tut
+{
+ namespace statics
+ {
+ /*------------------------------ data ------------------------------*/
+ // Although we're using types from the LLSD namespace, we're not
+ // constructing LLSD values, but rather instances of the C++ types
+ // supported by LLSD.
+ static LLSD::Boolean b{true};
+ static LLSD::Integer i{17};
+ static LLSD::Real f{3.14};
+ static LLSD::String s{ "hello" };
+ static LLSD::UUID uu{ "baadf00d-dead-beef-baad-feedb0ef" };
+ static LLSD::Date dt{ "2022-12-19" };
+ static LLSD::URI uri{ "http://secondlife.com" };
+ static LLSD::Binary bin{ 0x01, 0x02, 0x03, 0x04, 0x05 };
+
+ static std::vector<LLSD::String> quick
+ {
+ "The", "quick", "brown", "fox", "etc."
+ };
+
+ static std::array<int, 5> fibs
+ {
+ 0, 1, 1, 2, 3
+ };
+
+ // ensure that apply() actually reaches the target method --
+ // lack of ensure_equals() failure could be due to no-op apply()
+ bool called{ false };
+
+ /*------------------------- test functions -------------------------*/
+ void various(LLSD::Boolean b, LLSD::Integer i, LLSD::Real f, const LLSD::String& s,
+ const LLSD::UUID& uu, const LLSD::Date& dt,
+ const LLSD::URI& uri, const LLSD::Binary& bin)
+ {
+ called = true;
+ ensure_equals( "b mismatch", b, statics::b);
+ ensure_equals( "i mismatch", i, statics::i);
+ ensure_equals( "f mismatch", f, statics::f);
+ ensure_equals( "s mismatch", s, statics::s);
+ ensure_equals( "uu mismatch", uu, statics::uu);
+ ensure_equals( "dt mismatch", dt, statics::dt);
+ ensure_equals("uri mismatch", uri, statics::uri);
+ ensure_equals("bin mismatch", bin, statics::bin);
+ }
+
+ void strings(std::string s0, std::string s1, std::string s2, std::string s3, std::string s4)
+ {
+ called = true;
+ ensure_equals("s0 mismatch", s0, statics::quick[0]);
+ ensure_equals("s1 mismatch", s1, statics::quick[1]);
+ ensure_equals("s2 mismatch", s2, statics::quick[2]);
+ ensure_equals("s3 mismatch", s3, statics::quick[3]);
+ ensure_equals("s4 mismatch", s4, statics::quick[4]);
+ }
+
+ void ints(int i0, int i1, int i2, int i3, int i4)
+ {
+ called = true;
+ ensure_equals("i0 mismatch", i0, statics::fibs[0]);
+ ensure_equals("i1 mismatch", i1, statics::fibs[1]);
+ ensure_equals("i2 mismatch", i2, statics::fibs[2]);
+ ensure_equals("i3 mismatch", i3, statics::fibs[3]);
+ ensure_equals("i4 mismatch", i4, statics::fibs[4]);
+ }
+
+ void intfunc(int i)
+ {
+ called = true;
+ ensure_equals("i mismatch", i, statics::i);
+ }
+
+ void voidfunc()
+ {
+ called = true;
+ }
+ } // namespace statics
+
+ struct apply_data
+ {
+ apply_data()
+ {
+ // reset called before each test
+ statics::called = false;
+ }
+ };
+ typedef test_group<apply_data> apply_group;
+ typedef apply_group::object object;
+ apply_group applygrp("apply");
+
+ template<> template<>
+ void object::test<1>()
+ {
+ set_test_name("apply(tuple)");
+ LL::apply(statics::various,
+ std::make_tuple(statics::b, statics::i, statics::f, statics::s,
+ statics::uu, statics::dt, statics::uri, statics::bin));
+ ensure("apply(tuple) failed", statics::called);
+ }
+
+ template<> template<>
+ void object::test<2>()
+ {
+ set_test_name("apply(array)");
+ LL::apply(statics::ints, statics::fibs);
+ ensure("apply(array) failed", statics::called);
+ }
+
+ template<> template<>
+ void object::test<3>()
+ {
+ set_test_name("apply(vector)");
+ LL::apply(statics::strings, statics::quick);
+ ensure("apply(vector) failed", statics::called);
+ }
+
+ // The various apply(LLSD) tests exercise only the success cases because
+ // the failure cases trigger assert() fail, which is hard to catch.
+ template<> template<>
+ void object::test<4>()
+ {
+ set_test_name("apply(LLSD())");
+ LL::apply(statics::voidfunc, LLSD());
+ ensure("apply(LLSD()) failed", statics::called);
+ }
+
+ template<> template<>
+ void object::test<5>()
+ {
+ set_test_name("apply(LLSD scalar)");
+ LL::apply(statics::intfunc, LLSD(statics::i));
+ ensure("apply(LLSD scalar) failed", statics::called);
+ }
+
+ template<> template<>
+ void object::test<6>()
+ {
+ set_test_name("apply(LLSD array)");
+ LL::apply(statics::various,
+ llsd::array(statics::b, statics::i, statics::f, statics::s,
+ statics::uu, statics::dt, statics::uri, statics::bin));
+ ensure("apply(LLSD array) failed", statics::called);
+ }
+} // namespace tut