0

I wish to write a function, which uses the return value of a double function but/and I want to choose this double function during the function call. My idea is:

#include <iostream>

double linea(double a, double b, double x);
double parabola(double a, double b, double c, double x);

void konjgrad(double (*function)(void** params), void** params);

double linea(double a, double b, double x){
    return a*x+b;
}

double parabola(double a, double b, double c, double x){
    return a*x*x+b*x+c;
}

void konjgrad(double (*function)(void** params), void** params){
    double d; 
    d=function(params);
    std::cout<<d<<std::endl;
}

int main(){
    konjgrad(linea(1.6,5.1,2.6));    
    konjgrad(parabola(2.4,3.1,4,2.6));

    return 0;
}

But I stuck in the maze of the pointers. Has anyone an idea, how to solve the problem?

ilim
  • 4,477
  • 7
  • 27
  • 46
Tom Solid
  • 2,226
  • 1
  • 13
  • 32
  • Anyway. Possible solutions range from using a template to type erasure via `std::function`. You can pick whichever you find more appealing to your needs. – StoryTeller - Unslander Monica Jan 24 '17 at 11:32
  • 1
    konjgrad(linea(1.6,5.1,2.6)); - you're not passing a pointer to a function you're just calling the function and passing the result of it – xaxxon Jan 24 '17 at 11:32

2 Answers2

4

You can use variadic templates and perfect-forwarding to introduce the flexibility you require without any additional run-time overhead:

template <typename TF, typename... TArgs>
void konjgrad(TF&& f, TArgs&&... args)
{
    double d;
    d = std::forward<TF>(f)(std::forward<TArgs>(args)...);
    std::cout<<d<<std::endl;
}

konjgrad can then be called as follows:

konjgrad(linea, 1.6, 5.1, 2.6);    
konjgrad(parabola, 2.4, 3.1, 4, 2.6);

wandbox example


Alternatively, you could use lambda expressions and put the burden of binding the arguments on the caller:

template <typename TF>
void konjgrad(TF&& f)
{
    double d;
    d = std::forward<TF>(f)();
    std::cout<<d<<std::endl;
}

konjgrad can then be called as follows:

konjgrad([]{ return linea(1.6, 5.1, 2.6); });    
konjgrad([]{ return parabola(2.4, 3.1, 4, 2.6); });

wandbox example

Community
  • 1
  • 1
Vittorio Romeo
  • 90,666
  • 33
  • 258
  • 416
  • 1
    @xaxxon: What if `f` only has an rvalue call overload? – Kerrek SB Jan 24 '17 at 11:36
  • If you're not going to capture anything in the lambda, there's no need to have konjgrad templated: http://melpon.org/wandbox/permlink/BRZvosFaV9NRYZhI – xaxxon Jan 24 '17 at 11:42
  • 2
    @xaxxon - Leaving it templated allows for more optimization room. Much can be inlined when tempaltes are involved. – StoryTeller - Unslander Monica Jan 24 '17 at 11:43
  • @xaxxon: there are a lot of tradeoffs in the way you pass callable objects to functions. [I wrote an article about different techniques with benchmarks](https://vittorioromeo.info/index/blog/passing_functions_to_functions.html) that may interest you. – Vittorio Romeo Jan 24 '17 at 11:44
  • @StoryTeller you can also spike your compile times and RAM usage. also, -flto. Even without that, you don't need a template to allow the compiler to inline code. – xaxxon Jan 24 '17 at 13:09
  • @VittorioRomeo I recommend rerunning your std::function benchmarks with a good implementation: https://probablydance.com/2013/01/13/a-faster-implementation-of-stdfunction/ Compile time and runtime performance are pretty darned impressive. – xaxxon Jan 24 '17 at 13:12
  • 1
    @xaxxon: regardless of the speed of `std::function`, its semantics mean "ownership". If you don't need ownership, don't use `std::function`. Also, "implementation faster than `std::function`" cannot beat "zero overhead" :) – Vittorio Romeo Jan 24 '17 at 13:17
  • Please be clear what you mean by "overhead". In the general case (i.e. if `konjgrad()` were larger) then each distinct instantiation contributes to the code footprint. In this specific case, it's likely to be inlined, and therefore no worse than any other inlined function. – Toby Speight Jan 24 '17 at 15:38
0

Edited Based upon Vittorio Romeo review, updated to pass vectors as const reference and added necessary assertion inside the linea and parabola APIs.

Here is little naive approach if you want to avoid variadic functions. As your function interface is clearly defined you can pass in vector or array of double type parameters in your linea and parabola functions.

#include <vector>

using namespace std;

double linea(const std::vector<double>& params)
{
    assert(params.size() == 3 /*Linear 3 parameters required*/);
    return params[0] * params[1] + params[2];
}

double parabola(const std::vector<double>& params)
{
    assert(params.size() == 4 /*Parabola 4 parameters required*/);
    return params[0] * params[3] * params[3] +
        params[1] * params[3] +
        params[2];
}

Little bit modified version of your konjgrad function, it is now returning the double value from the underlying call through function pointer. Please notice that first parameter clearly defines the function interface. Second parameter is the vector of doubles.

double konjgrad(double(*function)(const std::vector<double>&), const std::vector<double>& params)
{
    return function(params);//returning value from here
}

Finally here's how you should invoke above defined APIs.

int main(int argc, wchar_t* argv[])
{
    std::vector<double> vals;
    vals.push_back(1.6);
    vals.push_back(5.1);
    vals.push_back(2.6);
    double v1 = konjgrad(linea, vals);

    std::vector<double> parab;
    parab.push_back(2.4);
    parab.push_back(3.1);
    parab.push_back(4.0);
    parab.push_back(2.6);

    double v2 = konjgrad(parabola, parab);

    return 0;

}

Hope this would help.

A.B.
  • 1,554
  • 1
  • 14
  • 21
  • I downvoted this solution because you're unnecessarily allocating memory and copying `std::vector` instances around. You're also assuming that the vectors will have the correct size without `assert`ing - this is dangerous! Also, the caller syntax isn't nice at all. – Vittorio Romeo Jan 24 '17 at 13:19
  • Please notice the very first line of OP "I wish to write a function, which uses the return value of a double function but/and I want to choose this double function during the function call", I think that the answer is proposing solution for OP's question. Secondly, I understand your concern about vector not being passed as reference (and creating unnecessary copy on stack), just wanted to share the idea, so should I update the code then? and add assertion etc? – A.B. Jan 24 '17 at 15:05