6

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.

P i
  • 29,020
  • 36
  • 159
  • 267
  • Is each of the `table->fp_53`, `table->fp_54`, ... a function pointer of a different type every time that matches the expected signature ? – tux3 Jan 07 '15 at 14:05
  • @tux3 yes, maybe there are a dozen different possible signatures in total. But fp_15 and fp_16 might have the same signature. – P i Jan 07 '15 at 14:13
  • If you are using C varargs, you'd need to use `va_start` etc. You need a variadic template. – T.C. Jan 07 '15 at 14:21
  • 1
    @Pi not sure, something [like this](http://coliru.stacked-crooked.com/a/86ddb0a0dda4da0e) ? – Piotr Skotnicki Jan 07 '15 at 14:34
  • @PiotrS. My reading is that `fp_53` etc. must be a plain function pointer. – T.C. Jan 07 '15 at 14:38
  • oh, so it could be [this](http://coliru.stacked-crooked.com/a/9f4353f26c36dcf7) – Piotr Skotnicki Jan 07 '15 at 14:48
  • &Base::f53 is a function/function pointer/static member function, right ? If so I have a fairly clean solution now. – tux3 Jan 07 '15 at 15:57
  • Excellent answers to a poorly worded question, my apologies. I have rewritten the question [here](http://stackoverflow.com/questions/27826117/design-pattern-for-exception-safe-trampolines) even though it looks as though a couple of these answers will answer the new question. I've referenced the new question back to this one. – P i Jan 07 '15 at 19:55

3 Answers3

5
#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.

Piotr Skotnicki
  • 46,953
  • 7
  • 118
  • 160
  • Is there any way to avoid the duplication when calling? Other than a `#define TRAP(f_xx) &trap::call` (which I don't actually see any objection to). – P i Jan 07 '15 at 15:13
  • 3
    @Pi no, curently there is [no way](http://stackoverflow.com/a/27374370/3953764) without a macro – Piotr Skotnicki Jan 07 '15 at 15:19
  • @Pi There is a proposal to avoid the duplication, but the earliest that will be available is C++17. `template struct trap;` – Andrew Jan 07 '15 at 15:50
  • @Andrew that's basically what the linked answer states – Piotr Skotnicki Jan 07 '15 at 15:51
  • @PiotrS. Indeed, I must have missed. Thanks! – Andrew Jan 07 '15 at 15:54
  • @PiotrS. This answer is brilliant engineering! I have since re-asked (and answered) the question [here](http://stackoverflow.com/q/27826117/435129), since this question is not clearly worded. However, you still managed to figure out what I was trying to ask! Thankyou! – P i Jan 08 '15 at 00:20
  • @PiotrSkotnicki, is there any way I might be able to contact you regarding a proposal? I'm pi AT pipad (then org after a dot). – P i Sep 22 '17 at 14:56
4

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;
}
tux3
  • 7,171
  • 6
  • 39
  • 51
  • This is super-interesting. I'm currently testing whether I can implement this. Is g actually needed? – P i Jan 07 '15 at 16:16
  • 1
    Yes, g is the pointer to your original function (e.g. Base::f53), it needs to be a static or global, so that we can make a lambda that doesn't capture anything, only those are convertible to a function pointer. To put it simply trap_gen returns a pointer to the wrapper, and the wrapper uses a static variable, g, to remember where the original is. – tux3 Jan 07 '15 at 16:20
  • I've put a test case [here](http://coliru.stacked-crooked.com/a/4ede9f86be872ac7). The problem is that in my situation the base function isn't static. I've just realised my question is incorrect as I haven't mentioned this. Argh. Does this mean that this approach will no longer work? It is a beast of a problem. – P i Jan 07 '15 at 17:09
  • If your function isn't static, you're in a bit of trouble. I suppose I can try to hack something with std::bind if you give me an object to bind it to, but no promise. Right now your question doesn't make any sense anymore. This is theoretically impossible to get a function pointer to a non-static member function (at least not in a well-defined way). TL;DR: You can't call those functions without an object, so what object am I supposed to call it with ? – tux3 Jan 07 '15 at 17:26
  • You should probably start another question to clear up the original issue. It looks like you're having a X Y problem :) – tux3 Jan 07 '15 at 17:34
  • Right. I need to ask a new question. Thanks. – P i Jan 07 '15 at 17:55
  • 2
    This approach fails if `trap_gen` is used for another function with same signature (`static g` gets initialized only once), [demo here](http://coliru.stacked-crooked.com/a/aa67061c2c17f4ca), @Pi: fyi – Piotr Skotnicki Jan 07 '15 at 19:36
  • True, I wonder if this limitation is fixable. It's especially bad right now since it will fail silently if the same signature is sent twice. That should be fixable with an extra overload on some counter we'd increment automatically internally, I think. – tux3 Jan 07 '15 at 19:44
4
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.

T.C.
  • 133,968
  • 17
  • 288
  • 421
  • Thanks to both you and Piotr for these brilliant answers. The one remaining awkwardness is having to manually specify the signature of table_fp_xx. Might you be willing to elaborate on your last comment? What would be the theoretically cleanest call-syntax? – P i Jan 07 '15 at 15:35
  • @Pi You may want to look at my answer (that I spent so long writing). Unless I'm mistaken, I believe I have the only clean call syntax, of the form trap_gen<>(Base::f53). – tux3 Jan 07 '15 at 16:03