summaryrefslogtreecommitdiff
path: root/indra/llcommon/apply.h
blob: 9f4c26889550daf877c81dbafabf57ad6b619882 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
/**
 * @file   apply.h
 * @author Nat Goodspeed
 * @date   2022-06-18
 * @brief  C++14 version of std::apply()
 * 
 * $LicenseInfo:firstyear=2022&license=viewerlgpl$
 * Copyright (c) 2022, Linden Research, Inc.
 * $/LicenseInfo$
 */

#if ! defined(LL_APPLY_H)
#define LL_APPLY_H

#include <boost/type_traits/function_traits.hpp>
#include <cassert>
#include <tuple>

namespace LL
{

/**
 * USAGE NOTE:
 * https://stackoverflow.com/a/40523474/5533635
 *
 * If you're trying to pass apply() a variadic function, the compiler
 * complains that it can't deduce the callable type, presumably because it
 * doesn't know which arity to reify to pass.
 *
 * But it works to wrap the variadic function in a generic lambda, e.g.:
 *
 * @CODE
 * LL::apply(
 *     [](auto&&... args)
 *     {
 *         return variadic(std::forward<decltype(args)>(args)...);
 *     },
 *     args);
 * @ENDCODE
 *
 * Presumably this is because there's only one instance of the generic lambda
 * @em type, with a variadic <tt>operator()()</tt>.
 *
 * It's pointless to provide a wrapper @em function that implicitly supplies
 * the generic lambda. You couldn't pass your variadic function to our wrapper
 * function, for the same original reason!
 *
 * Instead we provide a wrapper @em macro. Sorry, Dr. Stroustrup.
 */
#define VAPPLY(FUNC, ARGS)                                          \
    LL::apply(                                                      \
        [](auto&&... args)                                          \
        {                                                           \
            return (FUNC)(std::forward<decltype(args)>(args)...);   \
        },                                                          \
        (ARGS))

/*****************************************************************************
*   apply(function, tuple)
*****************************************************************************/
#if __cplusplus >= 201703L

// C++17 implementation
using std::apply;

#else // C++14

// Derived from https://stackoverflow.com/a/20441189
// and https://en.cppreference.com/w/cpp/utility/apply
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))...);
}

template <typename CALLABLE, typename... ARGS>
auto apply(CALLABLE&& func, const std::tuple<ARGS...>& args)
{
    // std::index_sequence_for is the magic sauce here, generating an argument
    // pack of indexes for each entry in args. apply_impl() can then pass
    // those to std::get() to unpack args into individual arguments.
    return apply_impl(std::forward<CALLABLE>(func),
                      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)
{
    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(std::forward<CALLABLE>(func),
                 std::make_tuple(args[I]...));
}

/**
 * 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)
{
    constexpr auto arity = boost::function_traits<CALLABLE>::arity;
    assert(args.size() == arity);
    return apply_impl(std::forward<CALLABLE>(func),
                      args,
                      std::make_index_sequence<arity>());
}

} // namespace LL

#endif /* ! defined(LL_APPLY_H) */