Skip to content
Snippets Groups Projects
Commit c6826034 authored by Nat Goodspeed's avatar Nat Goodspeed
Browse files

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)
parent 324f0d9b
No related branches found
No related tags found
2 merge requests!3Update to main branch,!2Rebase onto current main branch
......@@ -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}")
......
......@@ -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) */
......@@ -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);
......@@ -332,6 +333,31 @@ class LLSDParam
T _value;
};
/**
* 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
......@@ -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
/**
* @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
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment