传递给没有可变参数的方法的C ++可变参数模板参数方法

时间:2016-01-30 16:37:15

标签: c++ function loops templates variadic

我有以下问题,我真的无法编译所有研究过的问题和文章:

在C ++中,是否可以使用一个带有可变参数模板参数的方法来指定参数类型(作为某个类型的in,out,in / out参数的元描述类型,通过值传递,通过地址等),循环遍历这些可变参数以实例化指定类型的变量,并将这些变量传递给模板参数中指针指定的函数,但这些函数没有可变参数?

编辑1

我在这里尝试详细说明,如伪代码

template <decltype(*Type::*Method), typename... Parameters>
static bool ExecuteMethod(JSContext *cx, unsigned argc, JS::Value *vp)
{
    JS::CallArgs args = CallArgsFromVp(argc, vp);

    loop through Parameters
    {
        Parameters[i]::Type p[i] <-- args[i];
    }

    ReturnType r = Method(p[0], p[1], p[2] .. p[n]); // the method does not have variadic parameters
...
}

方法可能如下:

int(*GetColor) ( int16 *color);
int(*GetFile) ( FilePath &file );
int(*WriteDocument) ( const FilePath &file, const char *fileFormatName, bool askForParms);

这来自包装需求。 C ++中缺少挑战,如.net中的反射。 通过以某种方式循环遍历可变参数,可以实例化异构对象的数组?大概。 但是如何将它们传递给没有可变参数的方法呢?我认为不可能将这些对象数组分配给上面这三个函数而没有显式包装器,不是吗?

编辑2

我有很多反馈,但很明显我不够具体。 我没有详细说明,因为过去因为过于具体而抱怨我。事实上,我没有简单的实现,我是一个普通人,不是懒惰,但我试图让后一个开发更快。

以下是问题的根源:我需要包装Adobe Illustrator API,它会公开数百个(如果不是数千个)指向结构中分组的函数的指针,称为 suites

我尝试使用SpiderMonkey创建一个javascript引擎。

我使用Visual Studio 2015编译器。

我的方法如下:

我有几个类来包装API,以便为所有套件添加SpiderMonkey的引擎对象。每个SpiderMonkey类都可以作为 jsData 调用,包装数据类型的Adobe SDK或套件 jsSuite

到目前为止,我使用了模板,因为SpiderMonkey强制我将每个函数添加到具有特定签名的自定义对象中,如下所示:

bool jsAIDocumentSuite::WriteDocument(JSContext *cx, unsigned argc, JS::Value *vp)
{
...
}

并将其添加到自定义对象中将如下所示:

const JSFunctionSpec jsAIDocumentSuite::fFunctions[] = {
...
    JS_FN("WriteDocument", jsAIDocumentSuite::WriteDocument, 3, 0),
...
}

JS_FN是一个SpiderMonkeyMacro。

实际上,到目前为止,这只是Adobe SDK的不到10%。

大多数是带有一个参数的getter和setter,通过值或地址或指针传递,所以我用通用函数替换它们,如下所示:

    template <typename jsType, typename jsReturnType, typename ReturnPrivateType = jsReturnType::PrivateType, typename jsParamType, typename ParamPrivateType = jsParamType::PrivateType, ReturnPrivateType(*Type::*Method)(ParamPrivateType&)>
    static bool GetByRefMethod(JSContext *cx, unsigned argc, JS::Value *vp)
    {
        JS::CallArgs args = CallArgsFromVp(argc, vp);

        try
        {
            ReturnPrivateType result;

            ParamPrivateType ppt;

            if (jsType::Suite() && (jsType::Suite()->*Method))
                result = (jsType::Suite()->*Method)(ppt);
            else
                return false; // TODO throw a meaningful error

            if ((jsReturnType::IsNoError(result)) && (argc > 0) && (args[0].isObject()))
            {
                JSObject *obj = &args[0].toObject();

                JSObject *value = NULL;
                if (!jsParamType::FromAIObject<jsParamType>(cx, &ppt, value))
                    return false;

                if (!value)
                    return false;

                jsProperty::SetProperty(cx, &obj, "value", value, true);
            }

            JSObject *obj = JS_NewObject(cx, &jsDataClass<jsReturnType>::fClass);

            JS_SetPrivate(obj, new ReturnPrivateType(result));

            args.rval().setObject(*obj);
        }
        EXCEPTION_CATCH_CONVERT();

        return true;
    }

有点复杂,不是吗?

以上相关内容是:

  • args 变量包含由其引擎传入的SpiderMonkey参数
  • 这里只传递一个参数, ppt
  • 返回类型是一个值,因此很容易处理

我使用宏来为其变体注入方法(也有几种简短的形式,在这里不那么有趣):

JS_FN(#GET_METHOD, (js##TYPE::GetByRefMethod<js##TYPE, RETURN_JS_TYPE, RETURN_PRIVATE_TYPE, PARAM_JS_TYPE, PARAM_PRIVATE_TYPE, &TYPE::GET_METHOD>), 1, 0)

我希望能够处理变量参数,根据更具哲学性但有趣的统计数据。这个想法可能与C ++相反,而不是像预期的那样。

我如何期待它:

我希望添加可变参数元信息,例如:

模板         static bool方法(JSContext * cx,unsigned argc,JS :: Value * vp)         {             JS :: CallArgs args = CallArgsFromVp(argc,vp);

        try
        {
            ReturnPrivateType result;

            *1st challenge: Loop through the variadic list of meta-parameters and create their corresponding object instances here and initialize the IN ones with values from the *args* collection passed by the SpiderMonkey engine*

            if (jsType::Suite() && (jsType::Suite()->*Method))
                result = (jsType::Suite()->*Method)(*2nd challenge: pass arguments here: probably by using a variadic macro?*);
            else
                return false; // TODO throw a meaningful error

            if ((jsReturnType::IsNoError(result)) && (argc > 0) && (args[0].isObject()))
            {
                JSObject *obj = &args[0].toObject();

                JSObject *value = NULL;
                if (!jsParamType::FromAIObject<jsParamType>(cx, &ppt, value))
                    return false;

                if (!value)
                    return false;

                jsProperty::SetProperty(cx, &obj, "value", value, true);
            }

            JSObject *obj = JS_NewObject(cx, &jsDataClass<jsReturnType>::fClass);

            JS_SetPrivate(obj, new ReturnPrivateType(result));

            args.rval().setObject(*obj);
        }
        EXCEPTION_CATCH_CONVERT();

        return true;
    }

正如你所看到的,它不像C ++预期的那样,它有点颠倒,试图避免编写模板来扣除参数,在这里,我先知道参数并尝试编写代码来生成正确的参数通过首先了解他们的元信息,我有一组清晰的类型,我承诺编写正确的代码来生成正确的包装器。我不需要对参数数据进行太多验证,因为事情通常在这个过程中没有巨大的业务逻辑。

编辑3

关于参数元信息,我可以编写一些带静态的类型来指定参数的数据类型,无论是返回类型,是IN,OUT还是IN / OUT参数, jsType 等.. 它们将是上面模板参数函数的可变列表。

5 个答案:

答案 0 :(得分:2)

我仍然很难理解你想要做什么,但这应该让你使用一个变量模板函数调用一个函数(没有变量参数),从数组中获取参数并允许转换操作应用于传递给函数之前的每个参数:

    #include <functional>

    template<typename T, typename JST> T getParam(const JST& a)
    {
        //Do whatever conversion necessary
        return a;
    }
    namespace detail
    {
        template<typename R, typename... Args, int... S> R jsCaller(std::function<R(Args...)> f, seq<S...>, const JS::CallArgs& args)
        {
            return f(getParam<Args, /*Whatever type should go here */>(args[S])...);
        }
    }
    //Actually use this to call the function and get the result
    template<typename R, typename... Args> R jsCall(std::function<R(Args...)> f, const JS::CallArgs& args)
    {
        return detail::jsCaller(f, GenSequence<sizeof...(Args)>(), args);
    } 

GenSequence扩展seq&lt; 0,1,2,...,N-1&gt;并可以按如下方式实施:

template<int... N>
struct seq {};

template<int N, int... S>
struct gens : gens<N-1, N-1, S...> {};

template<int... S>
struct gens<0, S...> 
{
    typedef seq<S...> type;
};

template<int N> using GenSequence<N> = typename gens<N>::type;

这将创建一个整数参数包,并使用它们扩展函数调用 - 请参阅this问题。

您可以使用jsCall调用您的方法:

Result r = jsCall((Method), args);

假设Method可以转换为std :: function-如果没有,你仍然可以通过创建一个符合std :: function的lambda来实现它。这会解决问题吗?

答案 1 :(得分:1)

我提出了以下C ++ 11解决方案,它提供了基本的想法。但是,它很容易改进,所以我欢迎提出建议。 Live test here

#include <iostream>
#include <tuple>
using namespace std;

// bar : does something with an arbitrary tuple
// (no variadic template arguments)
template <class Tuple>
void bar(Tuple t)
{
    // .... do something with the tuple ...
    std::cout << std::tuple_size<Tuple>::value;
}

// foo : takes a function pointer and an arbitrary number of other
// arguments
template <class Func, typename... Ts>
void foo(Func f, Ts... args_in)
{
    // construct a tuple containing the variadic arguments
    std::tuple<Ts...> t = std::make_tuple(args_in...);

    // pass this tuple to the function f
    f(t);
}

int main()
{
    // this is not highly refined; you must provide the types of the
    // arguments (any suggestions?)
    foo(bar<std::tuple<int, const char *, double>>, 123, "foobar", 43.262);
    return 0;
}

答案 2 :(得分:1)

编辑:看到你的&#34;编辑2&#34;后,我不相信这是正确的解决方案。但是,请将其留作参考。

我相信我也找到了一个可以吸引参考的潜在解决方案。向下滚动到&#34;编辑4&#34;部分。

如果您要询问是否可以动态检查模板参数类型,则可以。我将首先介绍如何根据是否满足指定条件来使用std::true_typestd::false_type进行重载,然后专门继续讨论您的问题。考虑一下:

#include <type_traits>

namespace SameComparison {
    // Credit for the contents of this namespace goes to dyp ( https://stackoverflow.com/a/20047561/5386374 )
    template<class T, class...> struct are_same : std::true_type{};

    template<class T, class U, class... TT> struct are_same<T, U, TT...> :
        std::integral_constant<bool, std::is_same<T, U>{} && are_same<T, TT...>{} >{};
} // namespace SameComparison

template<typename T> class SomeClass {
    public:
        SomeClass() = default;
        template<typename... Ts> SomeClass(T arg1, Ts... args);
        ~SomeClass() = default;

        void func(T arg1);
        template<typename U> void func(U arg1);
        template<typename... Ts> void func(T arg1, Ts... args);
        template<typename U, typename... Ts> void func(U arg1, Ts... args);

        // ...

    private:
        template<typename... Ts> SomeClass(std::true_type x, T arg1, Ts... args);
        template<typename... Ts> SomeClass(std::false_type x, T arg1, Ts... args);

        // ...
};

// Constructors:
// -------------
// Public multi-argument constructor.
// Passes to one of two private constructors, depending on whether all types in paramater pack match T.
template<typename T> template<typename... Ts> SomeClass<T>::SomeClass(T arg1, Ts... args) :
    SomeClass(SameComparison::are_same<T, Ts...>{}, arg1, args...) { }

// All arguments match.
template<typename T> template<typename... Ts> SomeClass<T>::SomeClass(std::true_type x, T arg1, Ts... args) { }

// One or more arguments is incorrect type.
template<typename T> template<typename... Ts> SomeClass<T>::SomeClass(std::false_type x, T arg1, Ts... args) {
    static_assert(x.value, "Arguments wrong type.");
}

/*
Note that if you don't need to use Ts... in the parameter list, you can combine the previous two into a single constructor:

template<typename T> template<bool N, typename... Ts> SomeClass<T>::SomeClass(std::integral_constant<bool, N> x, T arg1, Ts... args) {
    static_assert(x.value, "Arguments wrong type.");
}

x will be true_type (value == true) on type match, or false_type (value == false) on type mismatch.  Haven't thoroughly tested this, just ran a similar function through an online compiler to make sure it could determine N.
*/

// Member functions:
// -----------------
// Single argument, type match.
template<typename T> void SomeClass<T>::func(T arg1) {
    // code
}

// Single argument, type mismatch.
// Also catches true_type from multi-argument functions after they empty their parameter pack, and silently ignores it.
template<typename T> template<typename U> void SomeClass<T>::func(U arg1) {
    if (arg1 != std::true_type{}) {
        std::cout << "Argument " << arg1 << " wrong type." << std::endl;
    }
}

// Multiple arguments, argument 1 type match.
template<typename T> template<typename... Ts> void SomeClass<T>::func(T arg1, Ts... args) {
    func(arg1);
    func(args...);
//  func(SameComparison::are_same<T, Ts...>{}, vals...);
}

// Multiple arguments, argument 1 type mismatch.
template<typename T> template<typename U, typename... Ts> void SomeClass<T>::func(U arg1, Ts... args) {
//  if (arg1 != std::true_type{}) {
//      std::cout << "Argument " << arg1 << " wrong type." << std::endl;
//  }
    func(vals...);
}

首先,SameComparison::are_samestd::is_same的扩展名,将其应用于整个参数包。这是检查的基础,示例的其余部分显示了如何使用它。最后两个函数中评论的行显示了如何在那里应用它。

现在,具体到你的问题。既然你知道方法是什么,你可以为它们制作类似的比较结构。

int (*GetColor) ( int16_t *color);
int(*GetFile) ( FilePath &file );
int(*WriteDocument) ( const FilePath &file, const char *fileFormatName, bool askForParms);

可以......

namespace ParameterCheck {
    template<typename T, typename... Ts> struct parameter_match : public std::false_type {};

    // Declare (GetColor, int16_t*) valid.
    template<> struct parameter_match<int (*)(int16_t*), int16_t*> : public std::true_type {};

    // Declare (GetFile, FilePath&) valid.
    // template<> struct parameter_match<int (*)(FilePath&), FilePath&> : public std::true_type {}; // You'd think this would work, but...
    template<> struct parameter_match<int (*)(FilePath&), FilePath> : public std::true_type {}; // Nope!
    // For some reason, reference-ness isn't part of the templated type.  It acts as if it was "template<typename T> void func(T& arg)" instead.

    // Declare (WriteDocument, const FilePath&, const char*, bool) valid.
    // template<> struct parameter_match<int (*)(const FilePath&, const char*, bool), const FilePath, const char*, bool> : public std::true_type {};
    // template<> struct parameter_match<int (*)(const FilePath&, const char*, bool), const FilePath&, const char*, bool> : public std::true_type {};
    template<> struct parameter_match<int (*)(const FilePath&, const char*, bool), FilePath, const char*, bool> : public std::true_type {};
    // More reference-as-template-parameter wonkiness: Out of these three, only the last works.
} // namespace ParameterCheck

在这里,我们创建一个等同于std::false_type的通用案例结构,然后将其专门化,以便特定情况为true_type。这样做是告诉编译器,&#34;这些参数列表是好的,其他任何东西都不好,&#34;其中每个列表以函数指针开头,并以函数的参数结束。然后,你可以为你的来电者做这样的事情:

// The actual calling function.
template<typename Func, typename... Ts> void caller2(std::true_type x, Func f, Ts... args) {
    std::cout << "Now calling... ";
    f(args...);
}

// Parameter mismatch overload.
template<typename Func, typename... Ts> void caller2(std::false_type x, Func f, Ts... args) {
    std::cout << "Parameter list mismatch." << std::endl;
}

// Wrapper to check for parameter mismatch.
template<typename Func, typename... Ts> void caller(Func f, Ts... args) {
    caller2(ParameterCheck::parameter_match<Func, Ts...>{}, f, args...);
}

至于退货类型扣除...取决于您想要推断它的位置:

  • 根据内容确定变量类型:声明变量时使用auto
  • 从传递的函数返回类型确定返回类型:如果您的编译器与C ++ 14兼容,那很容易。只需使用auto即可。 [VStudio 2015和GCC 4.8.0(-std=c++1y)与auto返回类型兼容。]

前者可以这样做:

int i = 42;
int func1() { return 23; }
char func2() { return 'c'; }
float func3() { return -0.0f; }

auto a0 = i; // a0 is int.
auto a1 = func1(); // a1 is int.
auto a2 = func2(); // a2 is char.
auto a3 = func3(); // a3 is float.

然而,后者更为复杂。

std::string stringMaker() {
    return std::string("Here, have a string!");
}

int intMaker() {
    return 5;
}

template<typename F> auto automised(F f) {
    return f();
}

// ...

auto a = automised(stringMaker); // a is std::string.
auto b = automised(intMaker);    // a is int.

如果您的编译器与autodecltype(auto)返回类型不兼容......那么,它有点冗长,但我们可以这样做:

namespace ReturnTypeCapture {
    // Credit goes to Angew ( https://stackoverflow.com/a/18695701/5386374 )
    template<typename T> struct ret_type;

    template<typename RT, typename... Ts> struct ret_type<RT (*)(Ts...)> {
        using type = RT;
    };
} // namespace ReturnTypeCapture

// ...

std::string f1() {
    return std::string("Nyahaha.");
}

int f2() {
    return -42;
}

char f3() {
    return '&';
}

template<typename R, typename F> auto rtCaller2(R r, F f) -> typename R::type {
    return f();
}

template<typename F> void rtCaller(F f) {
    auto a = rtCaller2(ReturnTypeCapture::ret_type<F>{}, f);
    std::cout << a << " (type: " << typeid(a).name() << ")" << std::endl;
}

// ...

rtCaller(f1); // Output (with gcc): "Nyahaha. (type: Ss)"
rtCaller(f2); // Output (with gcc): "-42 (type: i)"
rtCaller(f3); // Output (with gcc): "& (type: c)"

此外,我们可以进一步简化它,并检查返回类型,而不用单独的包装器。

template<typename F> auto rtCaller2(F f) -> typename ReturnTypeCapture::ret_type<F>::type {
    return f();
}

template<typename F> void rtCaller(F f) {
    auto a = rtCaller2(f);
    std::cout << a << " (type: " << typeid(a).name() << ")" << std::endl;
}

// ...

rtCaller(f1); // Output (with gcc): "Nyahaha. (type: Ss)"
rtCaller(f2); // Output (with gcc): "-42 (type: i)"
rtCaller(f3); // Output (with gcc): "& (type: c)"
// Same output.

虽然坚持到底有真的丑陋但是,我们能做得更好吗?答案是......是的!我们可以使用别名声明来创建typedef,留下更清晰的名称。因此,最终结果如下:

namespace ReturnTypeCapture {
    // Credit goes to Angew ( https://stackoverflow.com/a/18695701/5386374 )
    template<typename T> struct ret_type;

    template<typename RT, typename... Ts> struct ret_type<RT (*)(Ts...)> {
        using type = RT;
    };
} // namespace ReturnTypeCapture
template <typename F> using RChecker = typename ReturnTypeCapture::ret_type<F>::type;

std::string f1() { return std::string("Nyahaha."); }
int f2() { return -42; }
char f3() { return '&'; }


template<typename F> auto rtCaller2(F f) -> RChecker<F> {
    return f();
}

template<typename F> void rtCaller(F f) {
    auto a = rtCaller2(f);
    std::cout << a << " (type: " << typeid(a).name() << ")" << std::endl;
}

所以现在,如果我们结合参数检查&amp;退货类型扣除......

// Parameter match checking.
namespace ParameterCheck {
    template<typename T, typename... Ts> struct parameter_match : public std::false_type {};

    // Declare (GetColor, int16_t*) valid.
    template<> struct parameter_match<int (*)(int16_t*), int16_t*> : public std::true_type {};

    // Declare (GetFile, FilePath&) valid.
    template<> struct parameter_match<int (*)(FilePath&), FilePath> : public std::true_type {};

    // Declare (WriteDocument, const FilePath&, const char*, bool) valid.
    template<> struct parameter_match<int (*)(const FilePath&, const char*, bool), FilePath, const char*, bool> : public std::true_type {};

    // Declare everything without a parameter list valid.
    template<typename T> struct parameter_match<T (*)()> : public std::true_type { };
} // namespace ParameterCheck

// Discount return type deduction:
namespace ReturnTypeCapture {
    // Credit goes to Angew ( https://stackoverflow.com/a/18695701/5386374 )
    template<typename T> struct ret_type;

    template<typename RT, typename... Ts> struct ret_type<RT (*)(Ts...)> {
        using type = RT;
    };
} // namespace ReturnTypeCapture

// Alias declarations:
template<typename F, typename... Ts> using PChecker = ParameterCheck::parameter_match<F, Ts...>;
template<typename F> using RChecker = typename ReturnTypeCapture::ret_type<F>::type;

// ---------------

int GetColor(int16_t* color);
int GetFile(FilePath& file);
int WriteDocument(const FilePath& file, const char* fileFormatName, bool askForParams);

std::string f1() { return std::string("Nyahaha."); }
int f2() { return -42; }
char f3() { return '&'; }

// ---------------
// Calling function (C++11):

// The actual calling function.
template<typename Func, typename... Ts> auto caller2(std::true_type x, Func f, Ts... args) -> RChecker<Func> {
    std::cout << "Now calling... ";
    return f(args...);
}

// Parameter mismatch overload.
template<typename Func, typename... Ts> auto caller2(std::false_type x, Func f, Ts... args) -> RChecker<Func> {
    std::cout << "Parameter list mismatch." << std::endl;
    return static_cast<RChecker<Func> >(0); // Just to make sure we don't break stuff.
}

// Wrapper to check for parameter mismatch.
template<typename Func, typename... Ts> auto caller(Func f, Ts... args) -> RChecker<Func> {
    // return caller2(ParameterCheck::parameter_match<Func, Ts...>{}, f, args...);
    return caller2(PChecker<Func, Ts...>{}, f, args...);
}

// ---------------
// Calling function (C++14):

// The actual calling function.
template<typename Func, typename... Ts> auto caller2(std::true_type x, Func f, Ts... args) {
    std::cout << "Now calling... ";
    return f(args...);
}

// Parameter mismatch overload.
template<typename Func, typename... Ts> auto caller2(std::false_type x, Func f, Ts... args) {
    std::cout << "Parameter list mismatch." << std::endl;
}

// Wrapper to check for parameter mismatch.
template<typename Func, typename... Ts> auto caller(Func f, Ts... args) {
    // return caller2(ParameterCheck::parameter_match<Func, Ts...>{}, f, args...);
    return caller2(PChecker<Func, Ts...>{}, f, args...);
}

我相信你应该能够获得你想要的功能。唯一需要注意的是,如果你这样做,你需要在ParameterCheck中明确声明有效的函数,方法是对函数&amp;进行模板特化。其参数列表源自std::true_type而非std::false_type。我不确定是否有办法获得真正的动态参数列表检查,但这是一个开始。

[我不确定您是否可以重载caller(),或者您是否明确需要使用caller2()。我通过模板参数重载caller()的所有尝试最终导致编译器崩溃;出于某种原因,它选择template<typename Func, typename... Ts> void caller(Func f, Ts... args)作为caller(std::true_type, f, args...)的更好匹配而不是template<typename Func, typename... Ts> caller(std::true_type x, Func f, Ts... args),即使后者在前者之前列出,并尝试递归扩展它直到内存不足。 (在两个在线gcc编译器上进行了测试:IdeoneTutorialsPoint's compiler(使用-std=c++11)。我不确定这是否是gcc问题,或者我是否有点失误关于模板匹配是如何工作的。不幸的是,online VStudio compiler已经关闭以进行维护,而我现在离线可用的唯一VS版本并不支持可变参数模板,所以我不能检查是哪种情况。)除非有人另有说明,或者说如何解决这个问题,否则最好只使用caller()作为封装器和放大器。 caller2()要做繁重的工作。]

此处几乎所有与您的问题相关的示例:here

另外,请注意,您无法轻松从参数包中提取单个参数。你可以使用递归来一次删除几个前面的参数,你可以使用它们在构造函数的初始化列表中初始化成员变量,你可以检查包中有多少个参数,你可以专门化它(就像我们为parameter_match)所做的那样,&amp;你可以将整个包传递给一个能够获得正确数量参数的函数,但我相信它现在就是这样。尽管效率更高,但这有时会使它们比C风格的varargs更加尴尬。但是,如果您的ExecuteMethod()参数列表包含一个函数和参数列表,而不是其他任何内容,那么这不是问题。只要参数匹配成功,我们就可以将整个包提供给传递的函数,没有问题。在这方面,我们可以将ExecuteMethod()重写成类似......

的内容
// Not sure what cx is, leaving it alone.
// Assuming you wanted ExecuteMethod to take parameters in the order (cx, function, function_parameter_list)...

// Parameter list match.
template<typename M, typename... Parameters>
static bool ExecuteMethodWorker(std::true_type x, JSContext* cx, M method, Parameters... params)
{
    auto r = method(params...);
    // ...
}

// Parameter list mismatch.
template<typename M, typename... Parameters>
static bool ExecuteMethodWorker(std::false_type x, JSContext* cx, M method, Parameters... params)
{
    // Handle parameter type mismatch here.
    // Omit if not necessary, though it's likely better to use it to log errors, terminate, throw an exception, or something.
}

// Caller.
template<typename M, typename... Parameters>
static bool ExecuteMethod(JSContext* cx, M method, Parameters... params)
{
    return ExecuteMethodWorker(PChecker<M, Parameters...>{}, cx, method, params...);
}

确保在ExecuteMethod()之前原型或定义工作程序函数,以便编译器可以正确解析调用。

(对于我可能在那里的任何地方错过的任何错字道歉,我有点累。)

编辑:我找到了传递对模板的引用的问题。似乎使用模板来确定类型确实删除了引用本身,因此对于引用引用的函数来说就像template<typename T> void func(T&)这样的符号。可悲的是,我还不确定如何解决这个问题。然而,我 ,提出了PChecker的新版本,它动态地反映使用引用类型的任何函数的类型。但是,到目前为止,您仍需要手动添加引用,而非const引用可能暂时无法正常工作。

namespace ParameterCheck {
    namespace ParamGetter {
        // Based on an answer from GManNickG ( https://stackoverflow.com/a/4693493/5386374 )

        // Turn the type list into a single type we can use with std::is_same.
        template<typename... Ts> struct variadic_typedef { };

        // Generic case, to catch passed parameter types list.
        template<typename... Ts> struct variadic_wrapper {
            using type = variadic_typedef<Ts...>;
        };

        // Special case to catch void parameter types list.
        template<> struct variadic_wrapper<> {
            using type = variadic_typedef<void>;
        };

        // Generic case to isolate parameter list from function signature.
        template<typename RT, typename... Ts> struct variadic_wrapper<RT (*)(Ts...)> {
            using type = variadic_typedef<Ts...>;
        };

        // Special case to isolate void parameter from function signature.
        template<typename RT> struct variadic_wrapper<RT (*)()> {
            using type = variadic_typedef<void>;
        };
    } // namespace ParamGetter

    template<typename... Ts> using PGetter = typename ParamGetter::variadic_wrapper<Ts...>::type;

    // Declare class template.
    template<typename... Ts> struct parameter_match;

    // Actual class.  Becomes either std::true_type or std::false_type.
    template<typename F, typename... Ts> struct parameter_match<F, Ts...> : public std::integral_constant<bool, std::is_same<PGetter<F>, PGetter<Ts...> >{}> {};

    // Put specialisations for functions with const references here.
} // namespace ParameterCheck

template<typename F, typename... Ts> using PChecker = ParameterCheck::parameter_match<F, Ts...>;

请参阅here

-

编辑2:好的,无法弄清楚如何获取传递函数的参数列表并直接使用它。 可能可以使用元组,也许使用其余的GManNickG代码(convert_in_tuple结构),但我还没有调查过它们,并且没有&#39 ; t真的知道如何同时从元组中获取整个类型列表,或者它是否可能。 [如果有其他人知道如何解决参考问题,请随时发表评论。]

如果您只使用引用来最小化传递开销,而不是实际更改数据,那么您应该没问题。但是,如果您的代码使用引用参数来修改参数指向的数据,那么我不确定如何帮助您。遗憾。

-

编辑3:看起来RChecker可能不是C ++ 11函数转发所必需的,我们显然可以使用decltype([function call])。所以......

// caller2(), using decltype.  Valid, as args... is a valid parameter list for f.
template<typename Func, typename... Ts> auto caller2(std::true_type x, Func f, Ts... args) -> decltype(f(args...)) {
    std::cout << "Now calling... ";
    return f(args...);
}

// Parameter mismatch overload.
// decltype(f(args...)) would be problematic, since args... isn't a valid parameter list for f.
template<typename Func, typename... Ts> auto caller2(std::false_type x, Func f, Ts... args) -> RChecker<Func> {
    std::cout << "Parameter list mismatch." << std::endl;
    return static_cast<RChecker<Func> >(0); // Make sure we don't break stuff.
}

// Wrapper to check for parameter mismatch.
// decltype(caller2(PChecker<Func, Ts...>{}, f, args...)) is valid, but would be more verbose than RChecker<Func>.
template<typename Func, typename... Ts> auto caller(Func f, Ts... args) -> RChecker<Func> {
    // return caller2(ParameterCheck::parameter_match<Func, Ts...>{}, f, args...);
    return caller2(PChecker<Func, Ts...>{}, f, args...);
}

但是,正如所指出的那样,decltype在找不到与其准确传递的函数调用相匹配的函数调用时会遇到问题。因此,对于调用caller2()的参数不匹配版本的任何情况,尝试使用decltype(f(args...))来确定返回类型可能会导致问题。 然而,我不确定C ++ 14中引入的decltype(auto)是否会出现这个问题。

此外,在C ++ 14兼容的编译器中,使用decltype(auto)而不仅仅auto进行自动返回类型确定更好; auto并不保留const - ness,volatile - ness或reference-ness,而decltype(auto)则保留// caller2(), using decltype(auto). template<typename Func, typename... Ts> decltype(auto) caller2(std::true_type x, Func f, Ts... args) { std::cout << "Now calling... "; return f(args...); } 。它既可以用作尾随返回类型,也可以用作普通返回类型。

decltype(auto)
声明变量时也可以使用

// Default functor. template<typename... Ts> struct Executor { }; // General case. template<typename M, typename ReturnType, typename... Params> struct Executor<M, ReturnType (*)(Params...)> { public: // Parameter match: bool operator()(M method, Params... params) { ReturnType r = method(params...); // ... } // Parameter mismatch: template<typename... Invalid_Params> bool operator()(M method, Invalid_Params... ts) { // Handle parameter type mismatch here. } }; // Special case to catch void return type. template<typename M, typename... Params> struct Executor<M, void (*)(Params...)> { public: // Parameter match: bool operator()(M method, Params... params) { method(params...); // ... } // Parameter mismatch: template<typename... Invalid_Params> bool operator()(M method, Invalid_Params... ts) { // Handle parameter type mismatch here. } }; // Variadic function-like macro to automatically create, use, and destroy functor. // Uncomment whichever one is appropriate for the compiler used. // (The difference being that Visual C++ automatically removes the trailing comma if the // macro has zero variadic arguments, while GCC needs a hint in the form of "##" to tell // it to do so.) // Also note that the "do { ... } while (false)" structure is used to swallow the trailing // semicolon, so it doesn't inadvertently break anything; most compilers will optimise it // out, leaving just the code inside. // (Source: https://gcc.gnu.org/onlinedocs/cpp/Swallowing-the-Semicolon.html ) // MSVC: // #define ExecuteMethod(C, M, ...) \ // do { \ // Executor<decltype(&M), decltype(&M)> temp; \ // C = temp(M, __VA_ARGS__); \ // } while (false) // GCC: #define ExecuteMethod(C, M, ...) \ do { \ Executor<decltype(&M), decltype(&M)> temp; \ C = temp(M, ##__VA_ARGS__); \ } while (false) 。有关详细信息,请参阅here

编辑4:我相信我可能找到了一个潜在的解决方案,可以使用functors正确保留传递的函数的参数列表。但是,它可能会或可能不会产生不必要的开销,我不确定。

ExecuteMethod(return_value_holder, function_name, function_parameter_list);

在这种情况下,您可以将其用作:

do {
    Executor<decltype(&function_name), decltype(&function_name)> temp;
    return_value_holder = temp(function_name, function_parameter_list);
} while (false);

扩展为......

Params...

有了这个,就不需要手动浏览参数包并确保每个参数包与传递的函数参数匹配。由于传递函数的参数列表实际上是作为Params...构建到Executor中,我们可以根据它传递的参数是否匹配Parmas...来简单地重载函数调用操作符。如果参数与函数匹配,则调用Invalid_Params...重载;如果他们不这样做,就会调用void超载。比真实的反射,IMO更尴尬,但它似乎正确地匹配所有。

请注意:

  1. 我不确定是否大量使用仿函数会导致任何性能或内存使用开销。我现在并不是那么熟悉他们。
  2. 我不知道是否可以结合一般情况和&#34; ExecuteMethod()返回类型&#34;特殊情况成单个仿函数。当我尝试时,编译器抱怨,但我不确定它是否因为它不可能或因为我做错了。
  3. 考虑到#2,在修改此版本的Executor参数时,您必须对其进行修改,并将JSContext* cx的两个版本都匹配。
  4. 像这样,将template<typename M, typename ReturnType, typename... Params> struct Executor<M, ReturnType (*)(Params...)> { public: bool operator()(JSContext* cx, M method, Params... params); }; template<typename M, typename... Params> struct Executor<M, void (*)(Params...)> { public: bool operator()(JSContext* cx, M method, Params... params); }; #define ExecuteMethod(C, cx, M, ...) \ do { \ Executor<decltype(&M), decltype(&M)> temp; \ C = temp(cx, M, ##__VA_ARGS__); \ } while (false) 添加到参数列表中:

    ExecuteMethod()

    这可能是解决方案,但需要进一步测试,看它是否对性能有任何负面影响。至少,它确保int16_t保留了常量和引用,并且它比我以前的想法更清晰。

    请参阅here

    然而,可以进一步改进。由于我没有空间,请参阅here

    注意:

    1. std::int16_t(a.k.a。<cstdint>)位于标题std::true_type中。
    2. std::false_type<type_traits>位于标题{{1}}。

答案 3 :(得分:1)

[续第1部分:https://stackoverflow.com/a/35109026/5386374]

然而,存在一个问题。我们必须改变编写代码的方式来容纳ExecuteMethod(),这可能并不总是可行的。有没有解决方法,以便它的功能与之前指定的ExecuteMethod()完全相同,并且不需要将它修改的变量作为宏参数?答案是......是的!

// Variadic function-like macro to automatically create, use, and destroy functor.
// Uncomment whichever one is appropriate for the compiler used.
//  (The difference being that Visual C++ automatically removes the trailing comma if the
//   macro has zero variadic arguments, while GCC needs a hint in the form of "##" to tell
//   it to do so.)
// Instead of a do...while structure, we can just use a temporary Executor directly.
// MSVC:
// #define ExecuteMethod(M, ...) Executor<decltype(&M), decltype(&M)>{}(M, __VA_ARGS__)
// GCC:
#define ExecuteMethod(M, ...) Executor<decltype(&M), decltype(&M)>{}(M, ##__VA_ARGS__)

// For your example function WriteDocument(), defined as
//   int WriteDocument(const FilePath &file, const char *fileFormatName, bool askForParms);

bool c = ExecuteMethod(WriteDocument, file, fileFormatName, askForParams);

这一切都很好,但我们可以做一些更改,以简化事情而不影响性能。目前,这个仿函数只能接受函数指针(也许是lambda,我不熟悉它们的语法),而不是其他类型的函数对象。如果这是预期的,这意味着我们可以重写它以取消第一个模板参数(整个签名),因为第二个和第三个参数本身就是签名的组成部分。

// Default functor.
template<typename... Ts>
struct Executor { };

// General case.
template<typename ReturnType, typename... Params>
struct Executor<ReturnType (*)(Params...)> {
    private:
        // Instead of explicitly taking M as a parameter, create it from
        //  the other parameters.
        using M = ReturnType (*)(Params...);
    public:
        // Parameter match:
        bool operator()(M method, Params... params) {
            ReturnType r = method(params...);
            // ...
        }

        // Parameter mismatch:
        template<typename... Invalid_Params>
        bool operator()(M method, Invalid_Params... ts) {
            // Handle parameter type mismatch here.
        }
};

// Special case to catch void return type.
template<typename... Params>
struct Executor<void (*)(Params...)> {
    private:
        // Instead of explicitly taking M as a parameter, create it from
        //  the other parameters.
        using M = void (*)(Params...);
    public:
        // Parameter match:
        bool operator()(M method, Params... params) {
            method(params...);
            // ...
        }

        // Parameter mismatch:
        template<typename... Invalid_Params>
        bool operator()(M method, Invalid_Params... ts) {
            // Handle parameter type mismatch here.
        }
};

// Variadic function-like macro to automatically create, use, and destroy functor.
// Uncomment whichever one is appropriate for the compiler used.
//  (The difference being that Visual C++ automatically removes the trailing comma if the
//   macro has zero variadic arguments, while GCC needs a hint in the form of "##" to tell
//   it to do so.)
// Instead of a do...while structure, we can just use a temporary Executor directly.
// MSVC:
// #define ExecuteMethod(M, ...) Executor<decltype(&M)>{}(M, __VA_ARGS__)
// GCC:
#define ExecuteMethod(M, ...) Executor<decltype(&M)>{}(M, ##__VA_ARGS__)


// Note: If your compiler doesn't support C++11 "using" type aliases, replace them
//       with the following:
//           typedef ReturnType (*M)(Params...);

这会产生更干净的代码,但如上所述,它限制了仿函数只接受函数指针。

当像这样使用时,仿函数希望参数完全匹配。它可以正确处理参考和cv-ness,但可能与rvalues有问题,我不确定。请参阅here

关于如何在JSContext使用此功能......我真的不确定。我还没有了解过背景,所以其他人会更有帮助。我建议检查一下这里的其他答案是否会在你的情况下更加有用,一切都是诚实的。

注意:我不确定如果函数参数是函数,lambda,std::function或任何类型的函数,修改函数是多么容易。

注2:和以前一样,我不确定做这样的事情会对性能产生任何负面影响。这可能是一种更有效的方式,但我不知道它会是什么。

答案 4 :(得分:0)

很难从你的描述中看出来,但这是我对你问的最接近的解释:

auto foo(int) { cout << "foo int" << endl; }
auto foo(float) { cout << "foo float" << endl; }
//... other foo overloads...

template <class T>
auto uber_function(T t)
{
    foo(t);
}

template <class T, class... Args>
auto uber_function(T t, Args... args)
{
    foo(t);
    uber_function(args...);
}

auto main() -> int
{
    uber_function(3, 2.4f);
    return 0;
}

当然,这可以改进以获取引用,进行转发。这只是为了你有一个起点。由于你不太清楚,我无法给出更具体的答案。