Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using Lambda/Template/SFINAE to automate try/catch-safeguarding of trampoline functions

I have 100 or so trampoline functions. I would like to know whether it is possible to automate wrapping each one inside a try/catch block.

Please be warned in advance, this is not an easy question. I will start by describing the problem with (simplified) code, and will then attempt to answer it as best I can below, so the reader may see where I am at.

Foo has a function pointer table:

EDIT: This is a C function pointer table. So it could accept static W::w.
Signatures are here: http://svn.python.org/projects/python/trunk/Include/object.h

EDIT: I've attempted a test case here:

class Foo {
    Table table;
    Foo() {
        // Each slot has a default lambda.
        :
        table->fp_53 = [](S s, A a, B b)      -> int   {cout<<"load me!";};
        table->fp_54 = [](S s, C c, D d, E e) -> float {cout<<"load me!";};
        // ^ Note: slots MAY have different signatures
        //         only the first parameter 'S s' is guaranteed
    }

    // Foo also has a method for loading a particular slot:
    :
    void load53() { table->fp_53 = func53; }
    void load54() { table->fp_54 = func54; }
    :
}

If a particular slot is 'loaded', this is what gets loaded into it:

int func53(S s, A a, B b) { 
    try{
        return get_base(s)->f53(a,b);
    } 
    catch(...) { return 42;} 
}

float func54(S s, C c, D d, E e) { 
    try{
        return get_base(s)->f54(c,d,e);
    } 
    catch(...) { return 3.14;} 
}

I am trying to accomplish this using lambdas, so as to bypass having to define all of these func53 separately. Something like this:

class Foo {
    :
    void load53() { 
        table->fp_53 =
            [](S s, A a, B b)->int { return get_base(s)->f53(a,b); }
    }
    void load54() { 
        table->fp_54 =
            [](S s, C c, D d, E e)->float { return get_base(s)->f54(c,d,e); }
    }

However, this is failing to trap errors. I need to be putting a try/catch around the return statement:

try{ return get_base(s)->f53(a,b); } catch{ return 42; }

However, this creates a lot of clutter. It would be nice if I could do:

return trap( get_base(s)->f53(a,b); )

My question is: is there any way to write this trap function (without using #define)?


This is what I've come up with so far:

I think this would pass all the necessary information:

trap<int, &Base::f53>(s,a,b)

trap's definition could then look like this:

template<typename RET, Base::Func>
static RET 
trap(S s, ...) {
    try {
        return get_base(s)->Func(...);
    }
    catch {
        return std::is_integral<RET>::value ? (RET)(42) : (RET)(3.14); 
    }
}

This may allow for a very clean syntax:

class Foo {
    :
    void load53() { table->fp_53 = &trap<int,   &Base::f53>; }
    void load54() { table->fp_54 = &trap<float, &Base::f54>; }
}

At this point I'm not even sure whether some laws have been violated. table->fp_53 must be a valid C function pointer.

Passing in the address of a nonstatic member function (&Base::f53>) won't violate this, as it is a template parameter, and is not affecting the signature for trap

Similarly, ... should be okay as C allows varargs.

So if this is indeed valid, can it be cleaned up?

My thoughts are:

1) maybe the ... should be moved back to the template parameter as a pack.
2) maybe it is possible to deduce the return type for trap, and save one template parameter

3) that Base::Func template parameter is illegal syntax. And I suspect it isn't even close to something legal. Which might scupper the whole approach.

like image 624
P i Avatar asked Jan 07 '15 13:01

P i


3 Answers

#include <utility>

template <typename T, T t>
struct trap;

template <typename R, typename... Args, R(Base::*t)(Args...)>
struct trap<R(Base::*)(Args...), t>
{    
    static R call(int s, Args... args)
    {
        try
        {
            return (get_base(s)->*t)(std::forward<Args>(args)...);
        }
        catch (...)
        {
            return std::is_integral<R>::value ? static_cast<R>(42)
                                              : static_cast<R>(3.14); 
        }
    }
};

Usage:

table->fp_53 = &trap<decltype(&Base::f53), &Base::f53>::call;
table->fp_54 = &trap<decltype(&Base::f54), &Base::f54>::call;

DEMO


Note: std::forward can still be used although Args is not a forwarding reference itself.

like image 131
Piotr Skotnicki Avatar answered Oct 31 '22 05:10

Piotr Skotnicki


template<typename RET, typename... Args>
struct trap_base {
    template<RET (Base::* mfptr)(Args...)>
    static RET 
    trap(S s, Args... args) {
        try {
            return (get_base(s).*mfptr)(args...);
        }
        catch (...) {
            return std::is_integral<RET>::value ? (RET)(42) : (RET)(3.14); 
        }
    }
};

Usage:

void load53() { table.fp_53 = &trap_base<int, int>::trap<&Base::f53>; }
void load54() { table.fp_54 = &trap_base<float, int, float>::trap<&Base::f54>; }

Demo.

You can probably also use a partial specialization to extract RET and Args from decltype(&base::f53) etc.

like image 27
T.C. Avatar answered Oct 31 '22 04:10

T.C.


trap_gen is a function that returns a function pointer to a function generated on the fly, the equivalent of your trap function.

Here is how you use it

table->fp_53 = trap_gen<>(Base::f53);
table->fp_54 = trap_gen<>(Base::f54);
...

Where Base::f53 and Base::f54 are static member functions (or function pointers, or global functions in a namespace).

Proof of concept :

#include <iostream>

template<typename R, class...A> 
R (*trap_gen(R(*f)(A...)))(A...)
{
    static auto g = f;

    return [](A... a) 
    {
        try {
            return g(a...);
        } catch (...) {
            return std::is_integral<R>::value ? static_cast<R>(42)
                                              : static_cast<R>(3.14); 
        }
    };
}

int add(int a, int b)
{
  return a+b;
}


int main() {
    int(*f)(int, int) = trap_gen<>(add);
    std::cout << f(2, 3) << std::endl;

    return 0;
}
like image 4
tux3 Avatar answered Oct 31 '22 04:10

tux3