-->

C++ function caller wrapper using variadic pack ty

2019-03-03 08:34发布

问题:

I have studied a lot templates with variadic pack expansion, but still I can't compile all the articles I've found here in order to achieve my goal. Please accept my apologies for being, probably, redundant. I am bound to some APIs and I am tied to some function signatures like here:

static bool WrapperFunction(JSContext *cx, unsigned argc, JS::Value *vp)

I try to wrap objects and functions to use in javascript under SpiderMonkey. To integrate some C API, must be implemented wrappers for object data and wrapper methods for some object.

My solution lead me to the following logic of the wrapper, in order to be able to call methods with several arguments, but I don't know how to achieve it:

    template<typename jsType, typename jsReturnType, typename MethodType, MethodType Method, typename... jsParamType>
static bool VAMethodRet(JSContext *cx, unsigned argc, JS::Value *vp)
{
    JS::CallArgs args = CallArgsFromVp(argc, vp);

    jsReturnType::PrivateType result = jsReturnType::PrivateTypeDefaultValue();

Here begin my issues.

First step. Expand ...jsParamType... pack with calling a method for each jsParamType in order to create a wrapper class object instance for matching the corresponding argument out of args to prepare calling the C API function.

In other words, jsParamType tells the type it wraps so it can extract the C type object for each parameter to be passed to the C API function.

The first jsParamType corresponds to the args[0], the second jsParamType to the args1 etc., to the last jsParamType, which corresponds the args[argc].

It is possible to get less elements in args than sizeof...jsParamType, in this case the base C object should be initialized with a default value.

The meta-information of parameters or object wrappers is achieved with static methods already (jsParamType::jsType::PrivateTypeDefaultValue() for example).

Eventually, the expanded pack should be an array or vector of heterogeneous objects.

The matching function should be templated, based on jsParamType, but also get the index of the expanded variadic pack and the args local variable in order to get the correct object to parse - here is my first issue:

How to pass the index to the method?

I've tried to have some inspiration out of here, but I can't make it work: Calling a function for each variadic template argument and an array

Second step. After this, I plan to have a similar technique to here: Obtain Argument Index while Unpacking Argument List with Variadic Templates in order to call the C API function with the right arguments - is this possible?

Third step. In the end, based on a static function of jsParamType, called IsOut(), the out values will update the content of args local variable, but this should be done again using a new expansion, similar to the first step, to put back some values using type info present in jsParamType elements.

The last thing to do would be to set the return value, which is trivial.

Legend: jsType is a type of the struct containing the pointer to the C API functions, as these are grouped by the C API's provider. jsReturnType is the return type. (just for reference here...) Method is a C API method to be called, having the MethodType type.

Please help me to write such code, as I can't get it work. I don't ask to think for me and be my private coders. I just have no experience with this part of the preprocessor, I see lots of examples, many throw errors, or I can't extend them in order to achieve the above function. Thank you.

EDIT 1

Thanks for the reply, AndyG. Here is the more or less pseudo-code:

    // *jsType* is a type of the struct containing the pointer to the C API functions, as these are grouped by the C API's provider.
    // *jsReturnType* is the return type. (just for reference here...)
    // *Method* is a C API method to be called, having the *MethodType* type.
    // The non-template part of the function signature is needed untouched by SpiderMonkey

    template<typename jsType, typename jsReturnType, typename MethodType, MethodType Method, typename... jsParamType>
static bool VAMethodRet(JSContext *cx, unsigned argc, JS::Value *vp)
{
    // get the collection of passed arguments (SpiderMonkey stuff), in args, each element accessible by index
    // argc is the number of parameters passed from javascript
    WrappersCollection args = GetCallArgsFromVp(argc, vp);

    jsReturnType::PrivateType result = jsReturnType::PrivateTypeDefaultValue(); // creates an instance of a return value wrapper to receive the return value later

    // 1. Match wrappers in the WrappersCollection against their types in order to get their C type values
    CDataCollection cdata = array or vector of heterogeneous objects (int, char*, structs etc.);
    for each (jsParamType in the variadic pack ...)
    {
         cdata[jsParamTypeIndex] = Match<jsParamType, jsParamTypeIndex>(args);
    }
    // the above should be done by expanding the pack somehow
    // with emphasis on checking if argc[jsParamTypeIndex] was passed actually, because it's possible to have jsParamTypeIndex > argc
    // I don't know how to do it otherwise, by matching some param types variadic pack against a variadic pack of data arguments with different number of elements

    // 2. Transform the cdata collection into a variadic parameters pack in order to pass them to the C function.
    // The amount of args is specified by the function
    result = CFunction(cdata...);

    // 3. Update returned data in *out* params in the collection of wrapper objects received in the first place
    // index of the param in pack is necessary again
    // due to the different number of elements in the param types variadic pack versus the variadic pack of data arguments
    for each (jsParamType in the variadic pack ...)
    {
         if (jsParamType::IsOUT())
         {
              cdata[jsParamTypeIndex] = MatchBack<jsParamType, jsParamTypeIndex>(args);
         }
    }

    // the rest of things to do
    MatchReturnValue(result);

    return true; // SpiderMonkey needs to know the success of the operation
}

Is this more comprehensible enough?

回答1:

Thanks to the help and patience of AndyG, I have achieved my goal. Here is a sample of code with the note that actual wrappers are not provided, as they are specific from case to case. So, they are simulated by simply passing parameters.

#include <iostream>
#include <functional>
#include <tuple>
#include <type_traits>
#include <vector>
using namespace std;

#include <functional>
#include <tuple>
#include <type_traits>
using namespace std;

template<typename T, typename U, std::enable_if_t<std::is_same<T, U>::value, int> = 0>
T convert_type(U _in)
{
    //return const_cast<U>(_in);
    return _in;
}

template<typename T, typename U, std::enable_if_t<std::is_same<T, std::add_const_t<U>>::value, int> = 0>
T convert_type(U _in)
{
    //return const_cast<U>(_in);
    return _in;
}


// these conversion functions only can convert type to pointer to type, else return reference to type, so they're a bit limited
// pointer to pointer, or
template<typename T, typename U, std::enable_if_t<std::is_same<T, std::add_const_t<U>>::value, int> = 0>
T& convert_type(U& _in)
{
    return _in;
}

template<typename T, typename U, std::enable_if_t<std::is_same<T, std::add_lvalue_reference_t<U>>::value, int> = 0>
T& convert_type(U& _in)
{
    return _in;
}

template<typename T, typename U, std::enable_if_t<std::is_same<T, std::add_lvalue_reference_t<std::add_const_t<U>>>::value, int> = 0>
T& convert_type(U& _in)
{
    return _in;
}


// for conversion to pointer
//T&* to T*
template<typename T, typename U, std::enable_if_t<std::is_same<T, std::add_pointer_t<U>>::value, int> = 0>
T convert_type(U& _in)
{
    return std::addressof(_in);
}

template<typename T, typename U, std::enable_if_t<std::is_same<T, std::add_const_t<U>>::value, int> = 0>
T convert_type(U& _in)
{
    return std::addressof(_in);
}

template<typename T, typename U, std::enable_if_t<std::is_same<T, std::add_pointer_t<std::add_const_t<U>>>::value, int> = 0>
T convert_type(U& _in)
{
    return std::addressof(_in);
}

template<typename T>
struct function_traits;

template<typename R, typename ...Args>
struct function_traits<std::function<R(Args...)>>
{
    static const size_t nargs = sizeof...(Args);

    typedef R result_type;

    template <size_t i>
    struct arg
    {
        typedef typename std::tuple_element<i, std::tuple<Args...>>::type type;
    };

    static const bool isGlobalOrStaticContainer = true;
    static const bool isClassContainer = false;
    static const bool isPointerContainer = false;
    static const bool isConstInClassContainer = false;
    static const bool returnsVoid = std::is_same<R, void>::value;
};

template<typename C, typename R, typename ...Args>
struct function_traits<std::function<R(*C::*)(Args...)>>
{
    static const size_t nargs = sizeof...(Args);

    typedef R result_type;

    template <size_t i>
    struct arg
    {
        typedef typename std::tuple_element<i, std::tuple<Args...>>::type type;
    };

    static const bool isGlobalOrStaticContainer = false;
    static const bool isClassContainer = false;
    static const bool isPointerContainer = true;
    static const bool isConstInClassContainer = false;
    static const bool returnsVoid = std::is_same<R, void>::value;
};

template<typename C, typename R, typename ...Args>
struct function_traits<std::function<R(C::*)(Args...)>>
{
    static const size_t nargs = sizeof...(Args);

    typedef R result_type;

    template <size_t i>
    struct arg
    {
        typedef typename std::tuple_element<i, std::tuple<Args...>>::type type;
    };

    static const bool isGlobalOrStaticContainer = false;
    static const bool isClassContainer = true;
    static const bool isPointerContainer = false;
    static const bool isConstInClassContainer = false;
    static const bool returnsVoid = std::is_same<R, void>::value;
};

template<typename C, typename R, typename ...Args>
struct function_traits<std::function<R(C::*)(Args...) const>>
{
    static const size_t nargs = sizeof...(Args);

    typedef R result_type;

    template <size_t i>
    struct arg
    {
        typedef typename std::tuple_element<i, std::tuple<Args...>>::type type;
    };

    static const bool isGlobalOrStaticContainer = false;
    static const bool isClassContainer = true;
    static const bool isPointerContainer = false;
    static const bool isConstInClassContainer = true;
    static const bool returnsVoid = std::is_same<R, void>::value;
};

template<typename ParamType> class Param
{
public:

    typedef ParamType Type;

    static const bool isOut = false;
};

template<typename ParamType> class ParamOut : public Param<ParamType>
{
public:

    static const bool isOut = true;
};

template<typename Type, typename ReturnType, typename MethodType, MethodType Method, typename ParamType, size_t paramIndex, typename... ParamTypes>
static bool UnwrapParameter(unsigned argc, std::vector<void*>& args, typename ParamType::Type &ppt)
{
    if (argc > paramIndex)
    {
        ppt = *((std::add_pointer_t<typename ParamType::Type>(args[paramIndex])));
    }

    return true;
}

template<typename Type, typename ReturnType, typename MethodType, MethodType Method, typename... ParamType, size_t... paramIndex>
static bool UnwrapParameters(unsigned argc, std::vector<void*>& args, std::tuple<typename ParamType::Type...>& params, std::index_sequence<paramIndex...>)
{
    bool r[] = { true, UnwrapParameter<Type, ReturnType, MethodType, Method, ParamType, paramIndex, ParamType...>(argc, args, std::get<paramIndex>(params))... };

    bool res = true;
    for (size_t i = 0; i < sizeof...(ParamType) + 1 && res == true; i++)
        res &= r[i];
    return res;
}

template<typename Type, typename ReturnType, typename MethodType, MethodType Method, typename... ParamType>
static bool UnwrapParameters(unsigned argc, std::vector<void*>& args, std::tuple<typename ParamType::Type...>& params)
{
    return UnwrapParameters<Type, ReturnType, MethodType, Method, ParamType...>(argc, args, params, std::make_index_sequence<sizeof...(ParamType)>{});
}


template<typename Type, typename ReturnType, typename MethodType, MethodType Method, typename ParamType, size_t paramIndex, typename... ParamTypes>
static bool WrapParameter(unsigned argc, std::vector<void*>& args, typename ParamType::Type &ppt)
{
    if (ParamType::isOut && (argc > paramIndex))
    {
        // Wrap them back - nothing to do here, in this example
    }

    return true;
}

template<typename Type, typename ReturnType, typename MethodType, MethodType Method, typename... ParamType, size_t... paramIndex>
static bool WrapParameters(unsigned argc, std::vector<void*>& args, std::tuple<typename ParamType::Type...>& params, std::index_sequence<paramIndex...>)
{
    bool r[] = { true, WrapParameter<Type, ReturnType, MethodType, Method, ParamType, paramIndex, ParamType...>(argc, args, std::get<paramIndex>(params))... };

    bool res = true;
    for (size_t i = 0; i < sizeof...(ParamType)+1 && res == true; i++)
        res &= r[i];
    return res;
}

template<typename Type, typename ReturnType, typename MethodType, MethodType Method, typename... ParamType>
static bool WrapParameters(unsigned argc, std::vector<void*>& args, std::tuple<typename ParamType::Type...>& params)
{
    return WrapParameters<Type, ReturnType, MethodType, Method, ParamType...>(argc, args, params, std::make_index_sequence<sizeof...(ParamType)>{});
}


template<typename Type, typename ReturnType, typename MethodType,
    typename std::enable_if<function_traits<std::function<typename std::remove_pointer<MethodType>::type>>::isPointerContainer, MethodType>::type Method,
    typename... ParamType, size_t... paramIndex>
    static ReturnType CallMethodRet(bool& success, Type* obj, std::tuple<typename ParamType::Type...>& params, std::index_sequence<paramIndex...>)
{
    if (!(obj && (obj->*Method)))
        success = false;

    return (obj->*Method)(convert_type<typename function_traits<std::function<typename std::remove_pointer<MethodType>::type>>::template arg<paramIndex>::type, typename ParamType::Type>(std::get<paramIndex>(params))...);
}

template<typename Type, typename ReturnType, typename MethodType,
    typename std::enable_if<function_traits<std::function<typename std::remove_pointer<MethodType>::type>>::isGlobalOrStaticContainer, MethodType>::type Method,
    typename... ParamType, size_t... paramIndex>
    static ReturnType CallMethodRet(bool& success, Type* obj, std::tuple<typename ParamType::Type...>& params, std::index_sequence<paramIndex...>)
{
    if (!(*Method))
        success = false;

    return (*Method)(convert_type<typename function_traits<std::function<typename std::remove_pointer<MethodType>::type>>::template arg<paramIndex>::type, typename ParamType::Type>(std::get<paramIndex>(params))...);
}

template<typename Type, typename ReturnType, typename MethodType,
    typename std::enable_if<function_traits<std::function<typename std::remove_pointer<MethodType>::type>>::isClassContainer, MethodType>::type Method,
    typename... ParamType, size_t... paramIndex>
    static ReturnType CallMethodRet(bool& success, Type* obj, std::tuple<typename ParamType::Type...>& params, std::index_sequence<paramIndex...>)
{
    if (!(obj && (Method)))
        success = false;

    return (obj->*Method)(convert_type<typename function_traits<std::function<typename std::remove_pointer<MethodType>::type>>::template arg<paramIndex>::type, typename ParamType::Type>(std::get<paramIndex>(params))...);
}

template <typename Type, typename ReturnType, typename MethodType, MethodType Method, typename... ParamType>
static ReturnType CallMethodRet(bool& success, Type* obj, std::tuple<typename ParamType::Type...>& params)
{
    return CallMethodRet<Type, ReturnType, MethodType, Method, ParamType...>(success, obj, params, std::make_index_sequence<sizeof...(ParamType)>{});
}


template<typename Type, typename ReturnType, typename MethodType,
    typename std::enable_if<!function_traits<std::function<typename std::remove_pointer<MethodType>::type>>::returnsVoid, MethodType>::type Method,
    typename... ParamType>
static bool ExecuteMethod(Type* obj, unsigned argc, std::vector<void*>& args, ReturnType& result)
{
    try
    {
        const unsigned numArgs = sizeof...(ParamType);

        std::tuple<typename ParamType::Type...> params = std::make_tuple(typename ParamType::Type()...);

        if (!UnwrapParameters<Type, ReturnType, MethodType, Method, ParamType...>(argc, args, params))
            return false;

        bool success = true;

        result = CallMethodRet<Type, ReturnType, MethodType, Method, ParamType...>(success, obj, params);

        if (!success)
           return false; // Throw method not found here

        if (!WrapParameters<Type, ReturnType, MethodType, Method, ParamType...>(argc, args, params))
            return false;
    }
    catch (...)
    {
        // whatever...
    }

    return true;
}

template<typename Type, typename ReturnType, typename MethodType,
    typename std::enable_if<function_traits<std::function<typename std::remove_pointer<MethodType>::type>>::returnsVoid, MethodType>::type Method,
    typename... ParamType>
    static bool ExecuteMethod(Type* obj, unsigned argc, std::vector<void*>& args)
{
    try
    {
        const unsigned numArgs = sizeof...(ParamType);

        std::tuple<typename ParamType::Type...> params = std::make_tuple(typename ParamType::Type()...);

        if (!UnwrapParameters<Type, ReturnType, MethodType, Method, ParamType...>(argc, args, params))
            return false;

        bool success = true;

        CallMethodRet<Type, ReturnType, MethodType, Method, ParamType...>(success, obj, params);

        if (!success)
            return false; // Throw method not found here

        if (!WrapParameters<Type, ReturnType, MethodType, Method, ParamType...>(argc, args, params))
            return false;
    }
    catch (...)
    {
        // whatever...
    }
    return true;
}

class O 
{
public:
    void func(int a, string b, bool& c, const char* d)
    {
        std::cout << "Successfully called func with in values " << a << "," << b << "," << c << " and " << d << std::endl;

        c = true;

        std::cout << "Successfully called func with out values " << a << "," << b << "," << c << " and " << d << std::endl;
    }

    int func_i(int a, string b, bool& c, const char* d)
    {
        std::cout << "Successfully called func with in values " << a << "," << b << "," << c << " and " << d << std::endl;

        c = false;

        std::cout << "Successfully called func with out values " << a << "," << b << "," << c << " and " << d << std::endl;

        return 1;
    }
};

int main() {

    int a = 1;
    string b = "string";
    bool c = false;
    const char* d = "char*";

    std::vector<void*> v {(void*)&a, (void*)&b, (void*)&c, (void*)&d};

    std::cout << std::endl;

    O o;

    std::cout << ExecuteMethod<O, void, void(O::*)(int, string, bool&, const char*), &O::func, Param<int>, Param<string>, ParamOut<bool>, Param<const char*>>(&o, v.size(), v);

    std::cout << std::endl << std::endl;

    int result = 0;
    std::cout << ExecuteMethod<O, int, int(O::*)(int, string, bool&, const char*), &O::func_i, Param<int>, Param<string>, ParamOut<bool>, Param<const char*>>(&o, v.size(), v, result) << std::endl;
    std::cout << result << std::endl;

    return 0;
}