C ++可变参数模板参数方法传递给没有可变参数的方法
C++ variadic template arguments method to pass to a method without variadic arguments
我有以下问题,我真的无法从所有研究的问题和文章中编译:
在C++中,是否可以有一个带有可变参数模板参数的方法,该参数指定参数类型(作为某种类型的输入,输出,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 编译器。
我的做法如下:
我有几个 classes 来包装 API 以便为所有套件添加到 SpiderMonkey 的引擎对象。每个SpiderMonkey class,都可以称为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%。
最多的是带有一个参数的getters和setters,通过值或地址或指针传递,所以我将它们替换为通用函数,如下所示:
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
- return类型是一个值,所以很容易处理
我使用宏在其变体中注入该方法(也有几种简短形式,这里不太有趣):
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
关于参数的元信息,我可以写几个静态类型来指定参数的数据类型,是return类型,是IN、OUT还是IN/OUT 参数,其 jsType 等。
它们将是上面模板参数函数的可变参数列表。
从你的描述中很难判断,但这是我最接近你所问内容的解释:
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;
}
当然这个可以改进借鉴,做转发。这只是为了让你有一个起点。由于你说的不是很清楚,我无法给出更具体的答案。
我想出了下面的 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;
}
编辑:在看到您的 "Edit 2" 之后,我认为这不是正确的解决方案。不过,留作参考。
我相信我也找到了一个可以捕获 reference-ness 的潜在解决方案。向下滚动到底部,到 "Edit 4" 部分。
如果您询问是否可以动态检查模板参数类型,您可以。我将从一个一般示例开始,说明如何根据是否满足指定条件使用 std::true_type
和 std::false_type
进行重载,然后再专门解决您的问题。考虑一下:
#include <type_traits>
namespace SameComparison {
// Credit for the contents of this namespace goes to dyp ( https://whosebug.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
在这里,我们创建了一个 general-case 结构,它等于 std::false_type
,然后对其进行特殊化,以便特定情况成为 true_type
。这样做是告诉编译器,"These parameter lists are good, anything else is bad," 每个列表都以函数指针开始,以函数的参数结束。然后,你可以为你的来电者做这样的事情:
// 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...);
}
至于return类型推导...那要看你想推导到哪里:
- 根据内容判断变量类型:声明变量时使用
auto
。
- 从传递的函数 return 类型中确定 return 类型:如果您的编译器与 C++14 兼容,那很容易。只需使用
auto
。 [VStudio 2015 和 GCC 4.8.0(-std=c++1y
)与 auto
return 类型兼容。]
前者可以这样做:
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.
如果您的编译器与 auto
或 decltype(auto)
return 类型不兼容...好吧,它有点冗长,但我们可以这样做:
namespace ReturnTypeCapture {
// Credit goes to Angew ( https://whosebug.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)"
此外,我们可以进一步简化它,并检查 return 类型 而无需 单独的包装器。
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://whosebug.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;
}
所以现在,如果我们结合参数检查和return类型推导...
// 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://whosebug.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<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 编译器上测试:Ideone, and TutorialsPoint's compiler (with -std=c++11
). I'm not sure if this is a gcc problem, or if I was a bit off about how template matching works. Unfortunately, the online VStudio compiler 正在维护,目前我离线可用的唯一 VS 版本不支持可变参数模板,所以我无法检查是哪种情况.) 除非有人另有说明,或说明如何解决该特定问题,否则最好只使用 caller()
作为包装器 & caller2()
来完成繁重的工作。]
此处几乎所有与您的问题相关的示例:here
另外请注意,您不能轻易地从参数包中提取单个参数。你可以使用递归一次去掉前面几个参数,你可以用它们来初始化构造函数初始化列表中的成员变量,你可以检查包中有多少参数,你可以专门化它(就像我们为parameter_match
), & 你可以将整个包传递给一个接受正确数量参数的函数,但我相信目前就是这样。尽管效率更高,但有时这会使它们比 C-style 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()
之前制作原型或定义工作函数,以便编译器可以正确解析调用。
(对于我可能在其中任何地方遗漏的任何打字错误表示歉意,我有点 t红色。)
编辑:我找到了传递模板引用的问题。似乎使用模板来确定类型确实确实删除了 reference-ness 本身,因此像 template<typename T> void func(T&)
这样的符号表示接受引用的函数。可悲的是,我还不确定如何解决这个问题。然而,我做了,提出了一个新版本的PChecker
,它动态地反映了不使用引用的任何函数的类型类型。不过到目前为止,您仍然需要手动添加引用,non-const 引用可能暂时无法正常工作。
namespace ParameterCheck {
namespace ParamGetter {
// Based on an answer from GManNickG ( https://whosebug.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...))
来确定 return 类型可能会导致问题。 但是,我不确定在 C++14 中引入的 decltype(auto)
是否会有这个问题。
此外,在 C++14 兼容的编译器中,使用 decltype(auto)
显然比 auto
自动确定 return 类型要好; auto
不保留 const
-ness、volatile
-ness 或 reference-ness,而 decltype(auto)
保留。它既可以用作尾随 return 类型,也可以用作普通 return 类型。
// 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,但它似乎可以正确匹配所有内容。
注意:
- 我不确定大量使用仿函数是否会导致任何性能或内存使用开销。我……目前对他们不是很熟悉。
- 我不知道是否可以将一般情况和“
void
return 类型”特殊情况组合成一个函子。当我尝试时编译器抱怨,但我不确定是因为它不可能还是因为我做错了。
- 考虑#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)
这可能是解决方案,但需要进一步测试以查看它是否对性能有任何负面影响。至少,它会确保 const-ness 和 reference-ness 被 ExecuteMethod()
保留下来,而且它比我以前的想法要干净得多。
参见 here。
但是,还可以进行进一步的改进。由于我不在 space,请参阅 here。
备注:
int16_t
(a.k.a.std::int16_t
) 在header <cstdint>
.
std::true_type
和 std::false_type
在 header <type_traits>
.
我仍然无法准确理解你想要做什么,但这应该让你调用一个函数(没有可变参数)使用可变模板函数,从数组中获取参数并允许转换操作在传递给函数之前应用于每个参数:
#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<0, 1, 2, ... , N - 1> 并且可以实现如下:
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 部分:]
但是有一个问题。我们不得不改变代码的编写方式以适应 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...);
这会产生更清晰的代码,但是,如前所述,将仿函数限制为仅接受函数指针。
像这样使用时,仿函数期望参数完全匹配。它可以正确处理 reference-ness 和 cv-ness,但我不确定右值可能有问题。参见 here。
至于如何在您的 JSContext
中使用它...老实说,我不确定。我还没有了解上下文,所以其他人会对此更有帮助。老实说,我建议检查此处的其他答案之一是否对您的情况更有用。
注意:如果函数参数是仿函数、lambda、std::function
或任何类似的东西,我不确定修改仿函数使其工作有多容易。
注意 2:和以前一样,我不确定这样做是否会对性能产生任何负面影响。可能有更有效的方法,但我不知道它会是什么。
我有以下问题,我真的无法从所有研究的问题和文章中编译:
在C++中,是否可以有一个带有可变参数模板参数的方法,该参数指定参数类型(作为某种类型的输入,输出,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 编译器。
我的做法如下:
我有几个 classes 来包装 API 以便为所有套件添加到 SpiderMonkey 的引擎对象。每个SpiderMonkey class,都可以称为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%。
最多的是带有一个参数的getters和setters,通过值或地址或指针传递,所以我将它们替换为通用函数,如下所示:
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
- return类型是一个值,所以很容易处理
我使用宏在其变体中注入该方法(也有几种简短形式,这里不太有趣):
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
关于参数的元信息,我可以写几个静态类型来指定参数的数据类型,是return类型,是IN、OUT还是IN/OUT 参数,其 jsType 等。 它们将是上面模板参数函数的可变参数列表。
从你的描述中很难判断,但这是我最接近你所问内容的解释:
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;
}
当然这个可以改进借鉴,做转发。这只是为了让你有一个起点。由于你说的不是很清楚,我无法给出更具体的答案。
我想出了下面的 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;
}
编辑:在看到您的 "Edit 2" 之后,我认为这不是正确的解决方案。不过,留作参考。
我相信我也找到了一个可以捕获 reference-ness 的潜在解决方案。向下滚动到底部,到 "Edit 4" 部分。
如果您询问是否可以动态检查模板参数类型,您可以。我将从一个一般示例开始,说明如何根据是否满足指定条件使用 std::true_type
和 std::false_type
进行重载,然后再专门解决您的问题。考虑一下:
#include <type_traits>
namespace SameComparison {
// Credit for the contents of this namespace goes to dyp ( https://whosebug.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
在这里,我们创建了一个 general-case 结构,它等于 std::false_type
,然后对其进行特殊化,以便特定情况成为 true_type
。这样做是告诉编译器,"These parameter lists are good, anything else is bad," 每个列表都以函数指针开始,以函数的参数结束。然后,你可以为你的来电者做这样的事情:
// 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...);
}
至于return类型推导...那要看你想推导到哪里:
- 根据内容判断变量类型:声明变量时使用
auto
。 - 从传递的函数 return 类型中确定 return 类型:如果您的编译器与 C++14 兼容,那很容易。只需使用
auto
。 [VStudio 2015 和 GCC 4.8.0(-std=c++1y
)与auto
return 类型兼容。]
前者可以这样做:
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.
如果您的编译器与 auto
或 decltype(auto)
return 类型不兼容...好吧,它有点冗长,但我们可以这样做:
namespace ReturnTypeCapture {
// Credit goes to Angew ( https://whosebug.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)"
此外,我们可以进一步简化它,并检查 return 类型 而无需 单独的包装器。
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://whosebug.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;
}
所以现在,如果我们结合参数检查和return类型推导...
// 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://whosebug.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<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 编译器上测试:Ideone, and TutorialsPoint's compiler (with -std=c++11
). I'm not sure if this is a gcc problem, or if I was a bit off about how template matching works. Unfortunately, the online VStudio compiler 正在维护,目前我离线可用的唯一 VS 版本不支持可变参数模板,所以我无法检查是哪种情况.) 除非有人另有说明,或说明如何解决该特定问题,否则最好只使用 caller()
作为包装器 & caller2()
来完成繁重的工作。]
此处几乎所有与您的问题相关的示例:here
另外请注意,您不能轻易地从参数包中提取单个参数。你可以使用递归一次去掉前面几个参数,你可以用它们来初始化构造函数初始化列表中的成员变量,你可以检查包中有多少参数,你可以专门化它(就像我们为parameter_match
), & 你可以将整个包传递给一个接受正确数量参数的函数,但我相信目前就是这样。尽管效率更高,但有时这会使它们比 C-style 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()
之前制作原型或定义工作函数,以便编译器可以正确解析调用。
(对于我可能在其中任何地方遗漏的任何打字错误表示歉意,我有点 t红色。)
编辑:我找到了传递模板引用的问题。似乎使用模板来确定类型确实确实删除了 reference-ness 本身,因此像 template<typename T> void func(T&)
这样的符号表示接受引用的函数。可悲的是,我还不确定如何解决这个问题。然而,我做了,提出了一个新版本的PChecker
,它动态地反映了不使用引用的任何函数的类型类型。不过到目前为止,您仍然需要手动添加引用,non-const 引用可能暂时无法正常工作。
namespace ParameterCheck {
namespace ParamGetter {
// Based on an answer from GManNickG ( https://whosebug.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...))
来确定 return 类型可能会导致问题。 但是,我不确定在 C++14 中引入的 decltype(auto)
是否会有这个问题。
此外,在 C++14 兼容的编译器中,使用 decltype(auto)
显然比 auto
自动确定 return 类型要好; auto
不保留 const
-ness、volatile
-ness 或 reference-ness,而 decltype(auto)
保留。它既可以用作尾随 return 类型,也可以用作普通 return 类型。
// 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,但它似乎可以正确匹配所有内容。
注意:
- 我不确定大量使用仿函数是否会导致任何性能或内存使用开销。我……目前对他们不是很熟悉。
- 我不知道是否可以将一般情况和“
void
return 类型”特殊情况组合成一个函子。当我尝试时编译器抱怨,但我不确定是因为它不可能还是因为我做错了。 - 考虑#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)
这可能是解决方案,但需要进一步测试以查看它是否对性能有任何负面影响。至少,它会确保 const-ness 和 reference-ness 被 ExecuteMethod()
保留下来,而且它比我以前的想法要干净得多。
参见 here。
但是,还可以进行进一步的改进。由于我不在 space,请参阅 here。
备注:
int16_t
(a.k.a.std::int16_t
) 在header<cstdint>
.std::true_type
和std::false_type
在 header<type_traits>
.
我仍然无法准确理解你想要做什么,但这应该让你调用一个函数(没有可变参数)使用可变模板函数,从数组中获取参数并允许转换操作在传递给函数之前应用于每个参数:
#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<0, 1, 2, ... , N - 1> 并且可以实现如下:
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 部分:
但是有一个问题。我们不得不改变代码的编写方式以适应 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...);
这会产生更清晰的代码,但是,如前所述,将仿函数限制为仅接受函数指针。
像这样使用时,仿函数期望参数完全匹配。它可以正确处理 reference-ness 和 cv-ness,但我不确定右值可能有问题。参见 here。
至于如何在您的 JSContext
中使用它...老实说,我不确定。我还没有了解上下文,所以其他人会对此更有帮助。老实说,我建议检查此处的其他答案之一是否对您的情况更有用。
注意:如果函数参数是仿函数、lambda、std::function
或任何类似的东西,我不确定修改仿函数使其工作有多容易。
注意 2:和以前一样,我不确定这样做是否会对性能产生任何负面影响。可能有更有效的方法,但我不知道它会是什么。