From 196e49c1f8bd58ab9ce81843c10d452284ca7569 Mon Sep 17 00:00:00 2001
From: Nat Goodspeed <nat@lindenlab.com>
Date: Mon, 19 Dec 2022 17:27:36 -0500
Subject: DRTVWR-558: Add unit test for VAPPLY().

Add to apply_test.cpp a collect() function that incrementally accumulates an
arbitrary number of arguments into a std::vector<std::string>. Construct a
std::array<std::string> to pass it, using VAPPLY().

Clarify in header comments that LL::apply() can't call a variadic function
with arguments of dynamic size: std::vector or LLSD. The compiler can deduce
how many arguments to pass to a function with a fixed argument list; it can
deduce how many arguments to pass to a variadic function with a fixed number
of arguments. But it can't compile a call to a variadic function with an
arguments data structure whose size can vary at runtime.

(cherry picked from commit ceed33396266b123896f7cfb9b90abdf240e1eec)
---
 indra/llcommon/apply.h              |  6 ++++-
 indra/llcommon/llsdutil.h           |  6 +++++
 indra/llcommon/tests/apply_test.cpp | 52 ++++++++++++++++++++++++++++++++++++-
 3 files changed, 62 insertions(+), 2 deletions(-)

diff --git a/indra/llcommon/apply.h b/indra/llcommon/apply.h
index 26753f5017..9f4c268895 100644
--- a/indra/llcommon/apply.h
+++ b/indra/llcommon/apply.h
@@ -108,7 +108,11 @@ auto apply_impl(CALLABLE&& func, const std::vector<T>& args, std::index_sequence
                  std::make_tuple(args[I]...));
 }
 
-// this goes beyond C++17 std::apply()
+/**
+ * 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.
+ */
 template <typename CALLABLE, typename T>
 auto apply(CALLABLE&& func, const std::vector<T>& args)
 {
diff --git a/indra/llcommon/llsdutil.h b/indra/llcommon/llsdutil.h
index eaf8825791..eddaa64bd2 100644
--- a/indra/llcommon/llsdutil.h
+++ b/indra/llcommon/llsdutil.h
@@ -31,6 +31,7 @@
 
 #include "llsd.h"
 #include <boost/functional/hash.hpp>
+#include <boost/type_traits.hpp>
 #include <cassert>
 
 // U32
@@ -601,6 +602,11 @@ auto apply_impl(CALLABLE&& func, const LLSD& array, std::index_sequence<I...>)
     return std::forward<CALLABLE>(func)(LLSDParam<LLSD>(array[I])...);
 }
 
+/**
+ * apply(function, LLSD) 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.
+ */
 template <typename CALLABLE>
 auto apply(CALLABLE&& func, const LLSD& args)
 {
diff --git a/indra/llcommon/tests/apply_test.cpp b/indra/llcommon/tests/apply_test.cpp
index 1f1085a702..28ee3f9c81 100644
--- a/indra/llcommon/tests/apply_test.cpp
+++ b/indra/llcommon/tests/apply_test.cpp
@@ -15,12 +15,27 @@
 #include "apply.h"
 // STL headers
 // std headers
+#include <iomanip>
 // external library headers
 // other Linden headers
-#include "../test/lltut.h"
 #include "llsd.h"
 #include "llsdutil.h"
 
+// for ensure_equals
+std::ostream& operator<<(std::ostream& out, const std::vector<std::string>& stringvec)
+{
+    const char* delim = "[";
+    for (const auto& str : stringvec)
+    {
+        out << delim << std::quoted(str);
+        delim = ", ";
+    }
+    return out << ']';
+}
+
+// the above must be declared BEFORE ensure_equals(std::vector<std::string>)
+#include "../test/lltut.h"
+
 /*****************************************************************************
 *   TUT
 *****************************************************************************/
@@ -54,6 +69,8 @@ namespace tut
         // ensure that apply() actually reaches the target method --
         // lack of ensure_equals() failure could be due to no-op apply()
         bool called{ false };
+        // capture calls from collect()
+        std::vector<std::string> collected;
 
         /*------------------------- test functions -------------------------*/
         void various(LLSD::Boolean b, LLSD::Integer i, LLSD::Real f, const LLSD::String& s,
@@ -101,6 +118,20 @@ namespace tut
         {
             called = true;
         }
+
+        // recursion tail
+        void collect()
+        {
+            called = true;
+        }
+
+        // collect(arbitrary)
+        template <typename... ARGS>
+        void collect(const std::string& first, ARGS&&... rest)
+        {
+            statics::collected.push_back(first);
+            collect(std::forward<ARGS>(rest)...);
+        }
     } // namespace statics
 
     struct apply_data
@@ -109,6 +140,7 @@ namespace tut
         {
             // reset called before each test
             statics::called = false;
+            statics::collected.clear();
         }
     };
     typedef test_group<apply_data> apply_group;
@@ -168,4 +200,22 @@ namespace tut
                               statics::uu, statics::dt, statics::uri, statics::bin));
         ensure("apply(LLSD array) failed", statics::called);
     }
+
+    template<> template<>
+    void object::test<7>()
+    {
+        set_test_name("VAPPLY()");
+        // Make a std::array<std::string> from statics::quick. We can't call a
+        // variadic function with a data structure of dynamic length.
+        std::array<std::string, 5> strray;
+        for (size_t i = 0; i < strray.size(); ++i)
+            strray[i] = statics::quick[i];
+        // This doesn't work: the compiler doesn't know which overload of
+        // collect() to pass to LL::apply().
+        // LL::apply(statics::collect, strray);
+        // That's what VAPPLY() is for.
+        VAPPLY(statics::collect, strray);
+        ensure("VAPPLY() failed", statics::called);
+        ensure_equals("collected mismatch", statics::collected, statics::quick);
+    }
 } // namespace tut
-- 
cgit v1.2.3