diff options
author | Nat Goodspeed <nat@lindenlab.com> | 2022-12-19 16:29:06 -0500 |
---|---|---|
committer | Nat Goodspeed <nat@lindenlab.com> | 2023-07-13 12:34:12 -0400 |
commit | c682603417e1ef8290aacf274ff49821bd204c0b (patch) | |
tree | 54d245703df0cd6ef1f59b8607b70b2bcd3ebeea | |
parent | 324f0d9b8abad3a74a7c19a6e28f8c77c76b3b83 (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.txt | 2 | ||||
-rw-r--r-- | indra/llcommon/apply.h | 23 | ||||
-rw-r--r-- | indra/llcommon/llsdutil.h | 81 | ||||
-rw-r--r-- | indra/llcommon/tests/apply_test.cpp | 171 |
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 |