Search code examples
c++c++11templatesvariadic-templatesperfect-forwarding

Perfect-forward non-T arguments while converting T-s


(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?


Solution

  • 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