编辑:看到您的“编辑 2”后,我认为这不是正确的解决方案。不过还是留作参考吧。
我相信我已经找到了一个潜在的解决方案,它也可以捕获参考性。向下滚动到底部的“编辑 4”部分。
如果您询问是否可以动态检查模板参数类型,您可以。我将从一个通用示例开始,说明如何使用std::true_type 和std::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.
如果您的编译器与 auto 或 decltype(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<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 和 TutorialsPoint'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<typename T> void func(T&) 这样的符号表示需要引用的函数。可悲的是,我还不确定如何解决这个问题。然而,我确实提出了一个新版本的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,但它似乎与所有内容都匹配。
注意:
- 我不确定是否随意使用仿函数会导致任何性能或内存使用开销。我……目前对它们还不是很熟悉。
- 我不知道是否可以将一般情况和“
void 返回类型”特殊情况组合成一个函子。当我尝试时编译器抱怨,但我不确定是因为它不可能还是因为我做错了。
- 考虑#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。
注意事项:
-
int16_t(又名std::int16_t)在标题<cstdint>中。
-
std::true_type 和 std::false_type 在标题 <type_traits> 中。