1

(This question follows from this answer)

I am trying to adapt a trampoline function that is currently just passing through a variable number of arguments.

I would like to have it convert any argument PyObject* pyob to Object{pyob}, but forward all other arguments through.

So (void* self, int, PyObject*, float) -> (int, Object, float)

In that example, the first self argument is stripped away. This always happens. Out of the remaining arguments, one of them is of type PyObject*, and hence requires conversion to Object.

Here is the function:

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(void* s, Args... args)
    {
        std::cout << "trap:" << typeid(t).name() << std::endl;
        try
        {
            return (get_base(s)->*t)(std::forward<Args>(args)...);
        }
        catch (...)
        {
            std::cout << "CAUGHT" << std::endl;
            return std::is_integral<R>::value ? static_cast<R>(-42) : static_cast<R>(-3.14); 
        }
    }
};

It appears not to be forwarding arguments. I think it is making a copy of each argument. I've tried:

call(void* s, Args&&... args) 

But that just generates compiler errors.

The complete test case is here

How can I fix the function to perfect-forward all arguments apart from those of type PyObject*, which it should convert?

Piotr Skotnicki
  • 46,953
  • 7
  • 118
  • 160
P i
  • 29,020
  • 36
  • 159
  • 267
  • Thread it through a function overloaded on `PyObject` and with the other overload being a no-op template that just forwards the argument back out? – Xeo Jan 08 '15 at 14:45
  • 1
    @Pi I'm not sure what is your concern here, having a non-explicit constructor will allow you to do this conversion implicitly. Also, generating the `call` function from given member function pointer already gives you a parameter of type `Object`, so why do you expect `PyObject*` to be passed in? Does that mean you want also to replace any occurrence of `Object` to `PyObject*` in `call` function signature, and then convert it to `Object` ? – Piotr Skotnicki Jan 08 '15 at 15:21
  • @PiotrS., You are ahead of me -- Yes! I didn't realise it when I wrote the question but that's exactly what I needed! – P i Jan 08 '15 at 16:40

2 Answers2

4

It appears not to be forwarding arguments

You can't perfectly-forward arguments of a function which is not a template, or which is invoked through a pointer to a function, like you do. Perfect-forwarding involves a template argument deduction, which doesn't take place when you invoke a function through a pointer - that pointer points to a concrete instantiation of a function template.

The std::forward<Args>(args) expression is there to possibly utilize a move-constructor to copy-initialize the parameters of the target function from those arguments of call that are passed by value (or by a hard-coded rvalue reference), or let them be bound by an rvalue reference - you won't need any more those instances, you are free to move-from them, saving at least one copy operation. (It could be as simple as static_cast<Args&&>(args)..., because it's just a reference collapsing).


I would like to have it convert any argument PyObject* pyob to Object{pyob}, but forward all other arguments through. How can I fix the function to perfect-forward all arguments apart from those of type PyObject*, which it should convert?

#include <utility>

template <typename T, typename U>
T&& forward_convert(U&& u)
{
    return std::forward<T>(std::forward<U>(u));
}

template <typename T>
Object forward_convert(PyObject* a)
{
    return Object{a};
}

// ...

return (get_base(s)->*t)(forward_convert<Args>(args)...);

To replace any occurrence of Object with PyObject* while creating the signature of call function, and only then conditionally forward or convert the arguments, you should do what follows:

template <typename T>
struct replace { using type = T; };

template <>
struct replace<Object> { using type = PyObject*; };

// you may probably want some more cv-ref specializations:
//template <>
//struct replace<Object&> { using type = PyObject*; };  

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(void* s, typename replace<Args>::type... args)
    {
        try
        {
            return (get_base(s)->*t)(forward_convert<typename replace<Args>::type>(args)...);
        }
        catch (...)
        {
            return std::is_integral<R>::value ? static_cast<R>(-42) : static_cast<R>(-3.14); 
        }
    }
};

DEMO

Piotr Skotnicki
  • 46,953
  • 7
  • 118
  • 160
  • Thankyou! Could you explain why such complexity is necessary for forward_convert? It looks as though it may be handling some edge case I don't need to worry about. (I have < 100 functions I need to wrap (typedefs [here](http://svn.python.org/projects/python/trunk/Include/object.h)), and they have obvious signatures. So I don't need to make a reusable component. Could I get away with something simpler? – P i Jan 08 '15 at 16:38
  • @Pi I made it much simpler – Piotr Skotnicki Jan 08 '15 at 19:09
  • This looks much nicer. I think it can be simplified [even further](http://ideone.com/uur8Ad) ... – P i Jan 08 '15 at 20:22
  • @Pi no, because your version doesn't attempt to **conditionally** move from arguments that are passed by value or an rvalue reference – Piotr Skotnicki Jan 08 '15 at 20:24
  • Sorry to say I can't get my head around the double forwarding or the use of 2 template parameters for the general forward_convert, and the unused T parameter in the specialisation. Is there any chance you could include some explanation in your answer? – P i Jan 08 '15 at 20:49
  • @Pi the unused template parameter `T`: 1. forces you to specify it in both overloads, 2. makes the second overload a template as well, so that the compiler can pick it when you call it like `forward_convert(PyObject*)`. the double forward is just because you wanted a simpler version: the forwarding reference produces an exact match – Piotr Skotnicki Jan 09 '15 at 10:17
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/68473/discussion-between-p-i-and-piotr-s). – P i Jan 09 '15 at 10:26
  • and btw. `T` itself is needed for making the `static_cast` (aka. `std::forward`) – Piotr Skotnicki Jan 09 '15 at 10:26
3

You have to change call to (Note that I introduce Ts in addition to Args).

template <typename ... Ts>
static R 
call(void* s, Ts&&... args)
{
    std::cout << "trap:" << typeid(t).name() << std::endl;
    try
    {
        return (get_base(s)->*t)(std::forward<Ts>(args)...);
    }
    catch (...)
    {
        std::cout << "CAUGHT" << std::endl;
        return std::is_integral<R>::value ? static_cast<R>(-42) : static_cast<R>(-3.14); 
    }
}
Jarod42
  • 203,559
  • 14
  • 181
  • 302