1

In Python, it is possible to store a function pointer with different number of arguments and store the arguments in a list and then unpack the list and call that function like:

def Func(x1, x2):
   return x1+x2
ArgList = [1.2, 3.22]
MyClass.Store(Func)
MyClass.FuncPtr(*ArgList)

Is it possible in c++ to do a similar thing?

For example, store the function with the variable-number of inputs and the values in a std::vector and call function pointer by that vector?

I don’t want to define the argument list as a vector.

JeJo
  • 30,635
  • 6
  • 49
  • 88
Roy
  • 65
  • 2
  • 15
  • 40
  • 1
    Not like in Python, as in Python, it's a list and a dict that are the actual arguments. You can create a variadic object though, but needs lots of scaffolding. – Matthieu Brucher Jul 27 '20 at 07:13
  • 2
    In C++17 you can do something like [this](https://godbolt.org/z/sxzc5s). But I'm not sure that's what you want. – Evg Jul 27 '20 at 07:17
  • 1
    `I don’t want to define argument list as vector.` - , Why not? –  Jul 27 '20 at 07:17
  • Thanks, is there some way of unpacking in c++? – Roy Jul 27 '20 at 07:17
  • @Evg Thanks, does std::apply work with variadic functions? – Roy Jul 27 '20 at 07:21
  • 2
    @Hesam, `std::apply` itself works with anything that can be invoked with the given number of arguments, but `std::function` should know the number of arguments at compile time. – Evg Jul 27 '20 at 07:22
  • @super Since that is the API provided to users and user wants to write the simplest form. – Roy Jul 27 '20 at 07:23
  • 1
    Python lets you mismatch parameters and arguments right up to the point where the function is being called. By default C++ will stop you much earlier, and it's surprisingly difficult to copy Python's flexibility. If I were using your API, I wouldn't want the flexibility to be wrong – Caleth Jul 27 '20 at 08:48

3 Answers3

4

Is it possible in c++ to do a similar thing?

If you know the arguments(types) at compile-time, of-course you can!

For, instance like/similar to what @Evg suggested, make the Func as a variadic-generic-lambda, and with the support of 's std::apply,(for future readers) you could do.

(See a demo)

#include <iostream>
#include <utility> // std::forward
#include <tuple>   // std::apply

constexpr auto func_lambda = [](auto&&... args) {
     return (std::forward<decltype(args)>(args) + ...); // fold expression
};

int main() 
{
   std::cout << std::apply(func_lambda, std::make_tuple(1, 2, 3));  // prints 6!
   return 0;
}

And now as per your demo code, you could pack the above idea into to a class-template. Here, you can avoid the use of std::function, as the Func will be the lambda you pass, and can be stored as compiler deduced lambda unspecified type.

(See Demo Online)

#include <iostream>
#include <utility> // std::forward
#include <tuple>   //std::apply, std::make_tuple

// `Func` as variadic-generic-lambda
constexpr auto func_lambda = [](auto&&... args) noexcept {
   return (std::forward<decltype(args)>(args) + ...);
};

template<typename Func>
class MyClass final
{
   Func mFunction;
public:
   explicit MyClass(Func func)  // Store: pass the lambda here!
      : mFunction{ func }
   {}

   template<typename... Args>  // to execute the `Func` with `Args`! 
   constexpr auto execute(Args&&... args) const noexcept
   { 
      return std::apply(mFunction, std::make_tuple(std::forward<Args>(args)...));
   }
};

int main() 
{
   MyClass myClass{ func_lambda };         // MyClass.Store(Func)
   std::cout << myClass.execute(1.2, 2.82, 3);  // MyClass.FuncPtr(*ArgList)
   return 0;
}

Output:

7.02
JeJo
  • 30,635
  • 6
  • 49
  • 88
  • Thanks, this is close to what I need but I want MyClass.execute to take a vector of double values. – Roy Jul 27 '20 at 18:43
  • 1
    @Hesam Sounds like some more extra works. In order to work with the above code, that would be a bit difficult/ even impossible. However, you can have a try by converting the `std::vector` to `std::tuple`. The following post might be helpful: [convert vector to tuple](https://stackoverflow.com/questions/28410697/c-convert-vector-to-tuple) – JeJo Jul 27 '20 at 19:27
  • 2
    @Hesam. Vector is run-time sized. You need to use something that is compile time sized (like std::tuple). – Martin York Jul 27 '20 at 20:25
3

Unfortunately it is not possible to call an arbitrary function passing arbitrary values in a complete dynamic way at runtime. At least some part of the C++ code must compile a static call to a function with the same exact signature.

The problem is that in C++ the parameter passing can only be handled by the compiler and even variadic functions (e.g. printf) where only inspection is performed at runtime normally require a different and simpler calling convention.

One technical problem is that modern ABIs are extremely complex and asymmetrical (e.g. some parameters in registers, some in special FPU registers, some in the stack) and are only handled at compile time.

What you can do is "wrap" the functions you want to be able to call in a function accepting for example an std::vector of generic values and then calling the function passing those parameters converted to the proper type, e.g. something like

Value myabs(const std::vector<Value>& args) {
    if (args.size() != 1) throw std::runtime_error("Bad arity");
    return Value(fabs(args[0].to<double>()));
}

then it is easy to dynamically call these wrappers because the signature is the same for all of them.

Those wrappers unfortunately must be written (or generated) for each of the functions you need to be able to call so the compiler can actually generate the code needed to call the regular C++ functions.

I am not a template expert, but C++ metaprogramming can be used to generate the wrappers instead of coding each of them manually or writing an external code generator.

This is a very basic example:

typedef Value (*FWrapper)(const std::vector<Value>&);

template<typename RV, typename T1, typename T2, RV (*f)(T1, T2)>
Value wrapper2(const std::vector<Value>& args) {
    if (args.size() != 2) throw std::runtime_error("Bad arity");
    return f(args[0].to<T1>(), args[1].to<T2>());
}

For example to wrap double atan2(double, double) you can simply write:

FWrapper myatan2 = &wrapper2<double, double, double, &atan2>;

I tried a bit without success to avoid passing explicitly the return value type and number and types of parameters and to extract them instead from the passed function pointer, but may be that is also possible or even practical with recent C++.

The idea is basically to invoke the compiler to build a specific function call on demand when doing a template instantiation.

6502
  • 112,025
  • 15
  • 165
  • 265
2

Not exactly the same thing but in C++ you can apply a combining function to a range of values to produce a single result. std::accumulate can help you here. However, it works with a given range, not variadic arguments.

#include <numeric>
#include <vector>
#include <functional>
#include <iostream>

class MyClass
{
public:
    template<typename F, typename T>
    T Execute(F &&fn, const T initialValue, const std::vector<T> &values)
    {
        auto result = std::accumulate(values.begin(), values.end(),
            initialValue, std::forward<F>(fn));
        return result;
    }

};

int main()
{
    MyClass cls;
    std::vector<double> values = { 1.2, 3.22 };
    auto sum = cls.Execute(std::plus<double>{}, 0.0, values);
    std::cout << "Sum = " << sum << std::endl;

    auto product = cls.Execute(std::multiplies<double>{}, 1.0, values);
    std::cout << "Product = " << product << std::endl;

    std::vector<int> values2 = { 10, 20 };
    auto subtractFrom200 = cls.Execute(std::minus<int>{}, 200, values2);
    std::cout << "Subtract from 200 = " << subtractFrom200 << std::endl;

    std::vector<std::string> mystrings = {"Hello", " ", " world", ". ", "Bye!"};
    auto concatenate = cls.Execute([](std::string a, std::string b){ return a + b ;}, std::string(), mystrings);
    std::cout << "Concatenation = " << concatenate << std::endl;

    std::vector<double> values3 = {100, 98, 3.5, 50};
    auto findMin = [](double a, double b){ return std::min(a, b); };
    auto lowest = cls.Execute(findMin, values3.front(), values3);
    std::cout << "Min = " << lowest << std::endl;
}

Demo

Note: the fourth parameter to std::accumulate is optional and if omitted it will return the sum of the values. However, you can provide your own binary function if you want to do something else, e.g. multiplication or subtraction.

jignatius
  • 6,304
  • 2
  • 15
  • 30
  • Thanks, this is nice approach but the issue is that it seems to work only for sum whereas the function can be very general. – Roy Jul 27 '20 at 18:40
  • 1
    @Hesam It won't only work for sum. See my updated examples above plus demo. I have used templates here so that you can provide any kind of binary function. `std::accumulate` is similar to `reduce` in Python. – jignatius Jul 27 '20 at 20:35