2

I'm looking for a way to implement a function class

template<class ValueType>
class Function { ... };

which can be constructed with a function pointer (or functor) that takes any number of arguments of type ValueType and returns ValueType. For example, given these functions:

double foo(double);
double bar(double, double);
int    baz(double, int);

a Function<double> object could be constructed with either foo or bar but not baz.

Then, a member function call will, given some container of ValueType (or an iterator), call the underlying function with the right number of arguments at runtime.

Is such thing possible?

Avidan Borisov
  • 3,235
  • 4
  • 24
  • 27
  • This is going to be quite difficult, because you're asking that a single object be able to transparently handle at runtime completely different function signatures. It would be much easier if the number of arguments was part of the template arguments so that you can solve most of the difficulties at compile time (with template helpers), but then it defeats your purpose I guess. – syam Sep 07 '13 at 09:12
  • 1
    I wonder what the use case for this is. I withdrew my reply for now until I get more clarification about it. – László Papp Sep 07 '13 at 09:19
  • @Laszlo Papp I'm writing an expression evaluator. I want to allow user-defined functions to be registered in the evaluator. Therefore, I need a general function class which can hold any function of the allowed form, and call it with the right number of arguments at run-time. – Avidan Borisov Sep 07 '13 at 09:27
  • Let me ask it more clearly: why do you need this expression evaluator? – László Papp Sep 07 '13 at 09:29
  • Why? Because I'd like to make one. – Avidan Borisov Sep 07 '13 at 09:31
  • 1
    IIUC you need to handle functions that have unspecified number of arguments of unspecified type and that information is only known at runtime. Besides fiddling with C-style varargs, which are not type-safe, you are looking for something that is closer to dynamic languages. If this fits your bill you could consider to embed [Lua](http://www.lua.org/) in your code, it has been designed for easy integration with C (especially) and C++ code. – LorenzoDonati4Ukraine-OnStrike Sep 07 '13 at 10:12
  • Why do _you_ need to extract the values from the container? Have the user write a class with a `call` method that takes a container of ValueTypes and return a ValueType, since he knows best how many arguments are required. You could even write a function `template typename magic::result_type wrap_userfunction(F f);` , but I doubt that writing this function and the supporting helper classes and template specializations outweights the effort it takes the user to write the wrapper himself. – mars Sep 07 '13 at 10:14

2 Answers2

3

It is indeed possible, you just have to "recursively" unpack arguments from the container and pass them to the function on the deepest level:

#include <cstddef>
#include <utility>
#include <stdexcept>
#include <functional>
#include <type_traits>
#include <vector>
#include <iostream>

namespace detail {
    template <std::size_t argument_count>
    struct arguments_unpacker {
        template <typename Type, typename Function, typename InputIterator, typename... UnpackedArguments>
        static Type unpack(Function&& function, InputIterator arguments_begin, InputIterator arguments_end, UnpackedArguments&&... unpacked_arguments) {
            if (arguments_begin == arguments_end) {
                throw std::invalid_argument("Not enough arguments.");
            }
            return arguments_unpacker<argument_count - 1>::template unpack<Type>(std::forward<Function>(function), std::next(arguments_begin), arguments_end, std::forward<UnpackedArguments>(unpacked_arguments)..., *arguments_begin);
        }
    };

    template <>
    struct arguments_unpacker<0> {
        template <typename Type, typename Function, typename InputIterator, typename... UnpackedArguments>
        static Type unpack(Function&& function, InputIterator arguments_begin, InputIterator arguments_end, UnpackedArguments&&... unpacked_arguments) {
            if (arguments_begin != arguments_end) {
                throw std::invalid_argument("Too many arguments.");
            }
            return function(std::forward<UnpackedArguments>(unpacked_arguments)...);
        }
    };

    template <typename MemberFunction>
    struct member_function_arity;

    template <typename Result, typename Class, typename... Arguments>
    struct member_function_arity<Result(Class::*)(Arguments...)> {
        static constexpr std::size_t value = sizeof...(Arguments); 
    };

    template <typename Result, typename Class, typename... Arguments>
    struct member_function_arity<Result(Class::*)(Arguments...) const> {
        static constexpr std::size_t value = sizeof...(Arguments); 
    };

    template <typename Function>
    struct function_arity : member_function_arity<decltype(&Function::operator())> {};

    template <typename Result, typename... Arguments>
    struct function_arity<Result(*)(Arguments...)> {
        static constexpr std::size_t value = sizeof...(Arguments);  
    };

    template <typename Result, typename... Arguments>
    struct function_arity<std::function<Result(Arguments...)>> {
        static constexpr std::size_t value = sizeof...(Arguments);  
    };
}

template <typename Type, typename InputIterator, typename Function>
std::function<Type(InputIterator, InputIterator)> variate(Function function) {
    using namespace detail;
    return [function](InputIterator arguments_begin, InputIterator arguments_end) {
        return arguments_unpacker<function_arity<Function>::value>::template unpack<Type>(function, arguments_begin, arguments_end);
    };
}

namespace demo {
    double a(double x0) {
        std::cout << "a(" << x0 << ")\n";
        return 0.0;
    }

    double b(double x0, double x1) {
        std::cout << "b(" << x0 << ", " << x1 << ")\n";
        return 0.0;
    }

    double c(double x0, double x1, double x2) {
        std::cout << "b(" << x0 << ", " << x1 << ", " << x2  << ")\n";
        return 0.0;
    }

    auto l = [](double x0) mutable {
        std::cout << "l(" << x0 << ")\n";
        return 0.0;
    };

    void run() {
        using it = std::vector<double>::const_iterator;
        auto va = variate<double, it>(&a);
        auto vb = variate<double, it>(&b);
        auto vc = variate<double, it>(&c);
        auto vl = variate<double, it>(l);
        std::vector<double> a1 = {1.0};
        std::vector<double> a2 = {1.0, 2.0};
        std::vector<double> a3 = {1.0, 2.0, 3.0};
        va(begin(a1), end(a1));
        vb(begin(a2), end(a2));
        vc(begin(a3), end(a3));
        vl(begin(a1), end(a1));
    }
}

int main()
{       
    demo::run();
    return 0;
}

Note that this requres explicitly supplying iterator type. I don't see how it would be possible to remedy that without writing some kind of type erasing any_iterator.

yuri kilochek
  • 12,709
  • 2
  • 32
  • 59
  • Wow! That's awesome. Looks like dark magic to me, but still awesome :). *Answer accepted*! – Avidan Borisov Sep 07 '13 at 17:01
  • It fails to variate lambdas. `auto vd = variate([](double) { return double(); });` won't compile. Any ideas? – Avidan Borisov Sep 07 '13 at 17:51
  • Ended up using [`function_traits`](http://stackoverflow.com/a/7943765/852045) instead of `function_arity`. Lambdas work now! :) – Avidan Borisov Sep 07 '13 at 18:44
  • @Avidanborisov You are right, fixed code accordingly. Also note the specialization for mutable lambdas, which [function_traits](http://stackoverflow.com/a/7943765/1554020) doesn't cover, though it is mentioned in comments. – yuri kilochek Sep 07 '13 at 19:08
  • 1
    Given the amount of contortions you have to go through to accomplish this, you should really rethink whether C++ is the right tool here. Lorenzo already suggested lua which can solve this in a much more maintainable way. But +1 for the crazy template black voodoo. – greatwolf Sep 07 '13 at 21:14
1
#include <functional>
#include <boost/any.hpp>


template<class ValueType>
struct Function {
    template <typename ...Args>
    Function(const std::function<ValueType(Args...)>& f)
    :   fn(f)
    {}

    template <typename ...Args>
    ValueType operator () (Args... args) {
        auto f = boost::any_cast<const std::function<ValueType(Args...)>&>(fn);
        return f(args...);
    }

    boost::any fn;
};

int a(int) { return 1; }
int b(int, double) { return 2; }

int main(int argc, char** argv)
{
    typedef std::vector<Function<int>> Functions;
    Functions functions {
        std::function<int(int)>(a),
        std::function<int(int, double)>(b)
    };
    std::cout << functions[0](1) << functions[1](1, 2.0) << std::endl;
}
  • I can't just invoke `operator()` on `Function`. I need to call the function with arguments extracted from some container at runtime. – Avidan Borisov Sep 07 '13 at 09:51
  • @Avidanborisov The whole problem is indeed passing an arbitrary number of parameters to the function *at runtime* without knowing at compile time how many parameters you need to deal with. How can you call a function if you don't know its signature at compile time? – syam Sep 07 '13 at 10:19