A fun C++17 fold expressions puzzle

I spent all day trying to solve a riddle, and my last resort now is the hive mind.

Let’s say I have an Array that contains values in different types. I know which types are in each array element at compile time. And now I want to call a function with arguments of those types using a function template. Something like this:

void myFunction(float theFloat, int theInt, double theDouble, String theString)
{
    // this just does whatever
}

template <typename... Args>
void callTheFunctionWithArgs(const Array<var>& args)
{
    // what needs to go here?
    // this is basically supposed to call the below function, which each argument
    // basically casted (implicitly or explicitly) to the correct type
    
    // it must probably look like this:
    myFunction( (NO_IDEA_WHAT_GOES_HERE, ...) );
}

void main()
{
    Array<var> varArgs{1.f, 2, 3.0, "Hi!"};

    callTheFunctionWithArgs<float, int, double, String>(varArgs);
}

I think I need some magic C++17 fold expression to pull this off. But I can’t wrap my head around how to properly write it. Anybody here have the necessary neurons to understand how that stuff works? All examples I can find just add arguments together.

Try this (untested):

namespace detail
{
template <size_t... i>
void callTheFunctionWithArgs (const Array<var>& args, const std::index_sequence<i...>&)
{
    myFunction (args[i] ...);
}
}

void callTheFunctionWithArgs (const Array<var>& args)
{
    constexpr auto numArgs = 4;
    jassert (numArgs == args.size());
    detail::callTheFunctionWithArgs (args, std::make_index_sequence<numArgs>());
}

This is actually not a fold expression (fold expressions are useful to e.g. call a function on each element of a parameter pack). Instead the main problem to solve here is that you need to define a pack of compile time static indices to access the array elements inside the function call by expanding these indices.

In order to be easy to understand solution assumes that

  1. it is okay for you to hard code the number of arguments expected
  2. all arguments are of types that are implicitly convertible from a juce::var

Are these constraints okay for your use-case?

Edit: Just for the sake of the challenge I quickly whipped up a solution for your exact request:

namespace detail
{
template <class... Args, size_t... i>
void callTheFunctionWithArgs (const juce::Array<juce::var>& args, const std::index_sequence<i...>&)
{
    static_assert (sizeof... (Args) == sizeof... (i));
    myFunction (static_cast<Args> (args[i]) ...);
}
}

template <class... Args>
void callTheFunctionWithArgs (const juce::Array<juce::var>& args)
{
    jassert (sizeof... (Args) == args.size());
    detail::callTheFunctionWithArgs<Args...> (args, std::index_sequence_for<Args...>());
}

Quickly tested in the context of a JUCE project that I had open. Note that this was tested with C++ 20 though, let me know if this should not work with C++ 17

Dude you’re awesome! :nerd_face:

This works, thanks a bunch for saving my day (and my ability to sleep tonight)! :+1: :vulcan_salute:

Glad to hear that it works for you :slight_smile:

Just as an addition, in case you need a solution that works well for arbitrary functions, I’d probably pass the function to call as additional argument. In C++ 17, the easiest way to achieve type safety here is using a std::function

namespace detail
{
template <class... Args, size_t... i>
void callWithArgs (const std::function<void (Args...)>& fn, const juce::Array<juce::var>& args, const std::index_sequence<i...>&)
{
    static_assert (sizeof... (Args) == sizeof... (i));
    fn (static_cast<Args> (args[i]) ...);
}
}

template <class... Args>
void callWithArgs (const std::function<void (Args...)>& fn, const juce::Array<juce::var>& args)
{
    jassert (sizeof... (Args) == args.size());
    detail::callWithArgs<Args...> (fn, args, std::index_sequence_for<Args...>());
}

void main()
{
    Array<var> varArgs {1.f, 2, 3.0, "Hi!"};

    callWithArgs<float, int, double, String> (myFunction, varArgs);
}

Yep, it’ll actually be part of a wrapper around a std::function callback that can be either called directly, or encoded and sent to another process to be executed there. There’s a nice inter-instance discovery and communication scheme around that. Might open source it if it works and doesn’t turn into a complete mess. :wink:

1 Like

If you know each type of argument at compile time, while using a runtime type erasure mechanism like juce::var ? It’s a waste of resources and you’ll possibly have to resort to asserts or runtime checks to validate myFunction is callable correctly. Pretty unsafe and ugly i would say

It’s part of an IPC scheme. The argument values need to be serialized and sent to a different process (same binary), then deserialized there to finally call the function.

But then you just deserialize blindly and call a method based on unvalidated inputs ? Seems rather risky, hope you have some sort of integrity check or something in place.

It’s not like I’m sending executable code or something, and of course inputs need to be validated (not any different with a text input field btw). There will also be some validation/signing/compatibility checking going on, obviously. Probably not enough to keep a sufficiently motivated hacker from maliciously making the plugin display a histogram that looks like a penis at some point, but you get the idea. You can safely assume that the tiny bit discussed in this thread is by no means the complete solution.