3

What could I use to make a function take N number of arguments, where N is not know at programming time but is fixed at compile time (it is template parameter, in fact)?

The function in question is an access function which lies in a performance critical path, so I'm looking for the least overhead possible.

What first comes in mind is std::initializer_list and although cheap, as I've been told, it's still an unneccessary object to create and copy. What's more, it has a funky way to access its elements with initializer_list::begin[i] (which is another object I don't care for) and does not constraint the number of arguments to N exactly, but that is a minor complaint.

Second, there are template parameter packs. Could those be a viable candidate? I'll have to employ recursion to access N values.

What I'm aiming for I tried to show in this pseudo-code:

template<int dim, class T> class linear_searchspace {

    template<T...args> operator() (args) {
        return std::tie(some_kinda_structure[args[0]], some_kinda_structure[args[1]], some_kinda_structure[args[3]]);
    }

};

Hoow could I bring it in a recursive form that would actualy work?

CLARIFICATION: args are supposed to be coordinates. Each coordinate is the index of a value in a dimension. So N coordinates get passed, N values will be returned. It is like accessing N vectors at the same time. I would like to add an offset to each argument which depends on the index of the argument, as it is stored an array with the the index of the offset corresponding to the index of the argument. The calculation will be simple arithmetic.

And what would be the appropriate return type? The structure this will access will most probably hold numeric values, tuples at most. Is a std::tuple the best I can do or is it possible to craft something more performant?

Regarding the arguments, anything goes, even macros. I would be very glad to hear what tricks you came up with over the years.

Piotr Skotnicki
  • 46,953
  • 7
  • 118
  • 160
TeaOverflow
  • 2,468
  • 3
  • 28
  • 40
  • 1
    It depends heavily on how you are using `args`. – T.C. Oct 19 '14 at 14:40
  • It is unclear what do you want to be returned from function? Maybe vector is ok, than initializer_list is your friend – Ation Oct 19 '14 at 14:45
  • 1
    In your pseudocode, the entire return can be done in a single pack expansion without requiring recursion, but we don't know if that applies to your real code as well. – T.C. Oct 19 '14 at 14:46
  • would something like this suffice: http://coliru.stacked-crooked.com/a/dae7a50cf9064e3e ? – Piotr Skotnicki Oct 19 '14 at 15:35
  • @T.C. Thank you for your code PiotrS. , but I need to access the argument values inside the pack, because I need to do something with them (a calculation) and I dont know of any other way than to recure through the pack – TeaOverflow Oct 19 '14 at 16:13
  • @TeaOverflow any sample code would be useful to see what calculations are performed – Piotr Skotnicki Oct 19 '14 at 16:17
  • @Ation I'd like to return N values, gathered from accessing N vectors with the constraint "as fast as possible" because the access funktion will be called VERY often – TeaOverflow Oct 19 '14 at 16:18
  • @TeaOverflow is the same calculation performed on each element indexed by `args`, or it depends on the index? – Piotr Skotnicki Oct 19 '14 at 16:21
  • @PiotrS. The calculation will be simple arithmetic. I just want a way to access all of the values in the pack. – TeaOverflow Oct 19 '14 at 16:22
  • @TeaOverflow you repeat the same operation for each argument by putting `...` at the end, e.g. `((some_kinda_structure[args] + 1) * 5)...` – Piotr Skotnicki Oct 19 '14 at 16:23
  • @TeaOverflow it's quite important whether the same operation will be applied to each element, or maybe the index is a part of the formula, and whether this should be applied to original values (so we can return references), or it will create temporaries (so new instances are needed). – Piotr Skotnicki Oct 19 '14 at 16:36
  • @PiotrS. Thanks, was not aware of `...` repeating for earch argument. Actually I would like to add an offset to each argument which does indeed depend on the index of the argument, as it is stored an array with the the index of the offset corresponding to the index of the argument – TeaOverflow Oct 19 '14 at 16:38

3 Answers3

4
template <std::size_t DIM, typename T>
class linear_searchspace
{
public:
    template <typename... Args>
    inline constexpr auto operator()(Args... args) const
        noexcept(noexcept(T{std::declval<T>()}))
        -> std::tuple<decltype((void)args, T{std::declval<T>()})...>
    {
        static_assert(sizeof...(Args) == DIM, "wrong number of indices!");
        using Tuple = std::tuple<decltype((void)args, T{std::declval<T>()})...>;
        return get<Tuple>(make_index_sequence<sizeof...(Args)>{}, args...);
    }

private:
    template <typename Tuple, typename... Args, std::size_t... Is>
    inline constexpr Tuple get(index_sequence<Is...>, Args... args) const
        noexcept(noexcept(T{std::declval<T>()}))
    {
        return Tuple((some_kinda_structure[args] + Is)...);
        //                                       ^^^^
        // some calculation for each element based on index (here simple addition)
    }

    T some_kinda_structure[DIM]; // implementation defined
};

(the implementation of index_sequence is in the demo)

DEMO

With the above solution one gains the highest performance with constexpr objects, since the entire operation is evaluated at a compile time:

int main()
{
    constexpr linear_searchspace<5, int> lsp{};

    // evaluated at compile-time
    constexpr auto t = lsp(0, 1, 2, 3, 4);
    static_assert(std::get<1>(t) == 1, "!");

    // evaluated at run-time
    auto t2 = lsp(4, 3, 2, 1, 0);
    assert(std::get<3>(t2) == 3);
}
Piotr Skotnicki
  • 46,953
  • 7
  • 118
  • 160
2
double data[]={1,2,3,4,5,6,7,8};
double& toy_map(int x){return data[x];}

template<class...Ts>
auto example(Ts&&...ts)
-> decltype(std::tie(toy_map(std::forward<Ts>(ts))...))
{
  static_assert(sizeof...(Ts)==5, "wrong parameter count");
  return std::tie(toy_map(std::forward<Ts>(ts))...);
}

Note that the order the toy_map is called is unspecified.

In C++14, remove the decltype line if you do not need SFINAE.

Replace 5 with N in your real code.

If you want overloading to be perfect, you will have to SFINAE the N check, but that is overkill usually.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
1

If your function parameters are all the same type, you can pass them by vector. Place all the data into a vector, then pass the vector to the function.

class Coordinate;

std::vector<Coordinate> my_function(const std::vector<Coordinate>& data)
{
  const unsigned int items_in_data = data.size();
  //...
  return data;
}

The vector is dynamic and it can tell you how many items are inside it.

The rule of thumb is that when a function requires many arguments, place the arguments in a structure or container and pass the structure or container.

Thomas Matthews
  • 56,849
  • 17
  • 98
  • 154