【问题标题】:C++ variadic template arguments method to pass to a method without variadic argumentsC++ 可变参数模板参数方法传递给没有可变参数的方法
【发布时间】:2016-05-08 08:26:00
【问题描述】:

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

在 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

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

这里是问题的根源:我需要包装 Adob​​e Illustrator API,它公开了成百上千个指向分组在结构中的函数的指针,称为 suites

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

我使用 Visual Studio 2015 编译器。

我的做法如下:

我有几个类来包装 API,以便为所有套件添加到 SpiderMonkey 的引擎对象。每个 SpiderMonkey 类,可以称为 jsData,包装 Adob​​e 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。

实际上,到目前为止,这还不到 Adob​​e 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++ 相悖,但并不像预期的那样。

我会怎么期待:

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

模板 静态布尔方法(JSContext *cx,无符号 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 等等。 它们将是上述模板参数函数的可变参数列表。

【问题讨论】:

  • 不描述,为什么不提供一个最小的代码示例
  • 天哪,这句话很难理解。感觉就像一场马拉松。把它分成句子、想法。是的,显示一些代码比描述代码要好得多。
  • 你能做的最好的事情是创建一个包含任意数量模板参数的 std::tuple,然后将其输入到一个模板方法中,该方法将通用 std::tuple 作为其非可变参数.至少,我认为这行得通。我会在一分钟内澄清这一点。
  • 你不能在当前 C++ 中实例化一个“变量包”,尽管你可以实例化一个元组,这通常足够接近。
  • 对不起,句子太复杂了。

标签: c++ function loops templates variadic-templates


【解决方案1】:

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

    #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 并且可以如下实现:

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);

假设方法可以转换为 std::function- 如果没有,您仍然可以通过制作符合 std::function 的 lambda 来实现。这能解决问题吗?

【讨论】:

  • 谢谢,这非常接近我的需要。
【解决方案2】:

[接第 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,但可能存在右值问题,我不确定。见here

至于如何将其与您的JSContext 一起使用...老实说,我不确定。我还没有了解上下文,所以其他人会对此更有帮助。老实说,我建议检查此处的其他答案之一是否对您的情况更有用。


注意:如果函子的函数参数是函子、lambda、std::function 或任何类似的东西,我不确定修改函子是否容易工作。


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

【讨论】:

    【解决方案3】:

    我想出了以下 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;
    }
    

    【讨论】:

      【解决方案4】:

      编辑:看到您的“编辑 2”后,我认为这不是正确的解决方案。不过还是留作参考吧。

      我相信我已经找到了一个潜在的解决方案,它也可以捕获参考性。向下滚动到底部的“编辑 4”部分。


      如果您询问是否可以动态检查模板参数类型,您可以。我将从一个通用示例开始,说明如何使用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_same 有一个 std::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。它的作用是告诉编译器,“这些参数列表是好的,其他的都是坏的”,每个列表都以函数指针开始,以函数的参数结束。然后,你可以为你的调用者做这样的事情:

      // 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;
      }
      

      那么现在,如果我们结合参数检查和返回类型推导......

      // 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 中有效,方法是对函数及其参数列表进行模板特化,派生自std::true_type 而不是std::false_type。我不确定是否有办法获得真正的动态参数列表检查,但这是一个开始。

      [我不确定您是否可以重载caller(),或者您是否也明确需要使用caller2()。我所有通过模板参数重载caller() 的尝试最终导致编译器崩溃;出于某种原因,它选择template&lt;typename Func, typename... Ts&gt; void caller(Func f, Ts... args) 作为caller(std::true_type, f, args...)template&lt;typename Func, typename... Ts&gt; 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),你可以将整个包传递给一个接受正确数量参数的函数,但我相信目前就是这样。尽管效率更高,但这有时会使它们比 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&lt;typename T&gt; void func(T&amp;) 这样的符号表示需要引用的函数。可悲的是,我还不确定如何解决这个问题。然而,我确实提出了一个新版本的PChecker,它可以动态反映使用引用类型的任何函数的类型。但是,到目前为止,您仍然需要手动添加引用,并且非常量引用目前可能无法正常工作。

      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 结构),但我没有研究过它们,也不知道如何获取整个类型同时从一个元组中列出,或者如果可能的话。 [如果其他人知道如何解决参考问题,请随时发表评论。]

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

      --

      编辑 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 或参考性,而 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) 也可以在声明变量时使用。请参阅here 了解更多信息。


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

      // 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)
      

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

      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... 的形式内置在 Executor 中,因此我们可以简单地根据传递的参数是否匹配 Params... 来重载函数调用运算符。如果参数与函数匹配,则调用Parmas...重载;如果他们不这样做,它会调用Invalid_Params... 重载。比真实反射更尴尬,IMO,但它似乎与所有内容都匹配。

      注意:

      1. 我不确定是否随意使用仿函数会导致任何性能或内存使用开销。我……目前对它们还不是很熟悉。
      2. 我不知道是否可以将一般情况和“void 返回类型”特殊情况组合成一个函子。当我尝试时编译器抱怨,但我不确定是因为它不可能还是因为我做错了。
      3. 考虑#2,当修改ExecuteMethod()的这个版本的参数时,你必须修改它并且Executor的两个版本匹配。

      像这样,JSContext* cx 被添加到参数列表中:

      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() 保留 const-ness 和 reference-ness,而且它比我的旧想法干净得多。

      here

      不过,还可以进行进一步的改进。由于空间不足,请参阅here


      注意事项:

      1. int16_t(又名std::int16_t)在标题&lt;cstdint&gt;中。
      2. std::true_typestd::false_type 在标题 &lt;type_traits&gt; 中。

      【讨论】:

      • 别抱歉,这是件微妙的事情。我可能会问太多:)。你在这里做了很多工作,我需要一些时间来吸收它。谢谢,我学完就回来。
      • @mike 好的,基本上...第一部分 (SomeClass) 是使用 &lt;type_traits&gt; 根据函数是否满足条件来重载函数的一般示例。第二部分 (ParameterCheck::parameter_match&lt;F, Ts...&gt;) 是我们如何使用它来确保函数的参数列表由函数指针和该函数采用的参数组成,方法是定义有效的特定情况并将其他所有内容标记为无效。
      • 第三部分(autoReturnTypeCapture::ret_type&lt;F&gt;)是如何自动判断变量类型和函数返回类型;对于后者,您可以将 auto 与任何与 C++14 兼容的编译器一起使用,但对于仅与 C++11 兼容的编译器,您需要类似 ReturnTypeCapture::ret_type 的东西。第 4 部分 (RChecker) 是我们如何制作模板化的 typedef 来缩短长名称(例如 ReturnTypeCapture::ret_type&lt;F&gt;::typeRchecker)。
      • 第 5 部分(RCheckerPChecker)将它们联系在一起,C++11 和 C++14 的示例都自动从传递函数的返回类型推断返回类型。第 6 部分(参数包讨论,您的 ExecuteMethod(),在第一个 Ideone 链接之后)是查看可变参数模板参数包的限制,以及如何将这些想法与您的函数一起使用的想法。我们不需要一次将参数与传递函数的参数列表进行比较,我们可以在一个操作中完成。
      • 第 7 部分(“编辑:”部分)是 PChecker 的动态版本,它压缩了 ExecuteMethod 的参数列表(不包括传递的函数或它之前的任何内容)和传递函数的参数列表为单一类型,因此我们可以动态比较它们,而无需手动指定什么是有效的,什么是无效的。 [它适用于除引用类型之外的所有内容,这就是为什么您仍然需要手动指定它们。]
      【解决方案5】:

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

      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;
      }
      

      当然这可以改进为取引用,进行转发。这只是给你一个起点。因为你说的不是很清楚,所以我无法给出更具体的答案。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2014-10-20
        • 2012-12-29
        • 2011-03-16
        • 2013-01-29
        • 2016-12-25
        • 1970-01-01
        • 2021-02-11
        相关资源
        最近更新 更多