这不起作用的原因是因为ARGS... 是一个可变参数包,当用于推导时,它只能在函数签名的end 处使用。例如,你可以推断:
void(*)(int,Args...)
但你无法推断
void(*)(Args...,int)
由于您的问题要求 last 参数是一种特定类型,并且要专门推导出倒数第二个参数,因此您需要推导出 func 的整个函数签名,并使用 SFINAE以防止使用错误的参数意外调用此函数。
为此,我们首先需要一种方法来提取nth 最后一个参数。一个简单的类型特征可以写成如下:
#include <type_traits>
// A simple type-trait that gets the Nth type from a variadic pack
// If N is negative, it extracts from the opposite end of the pack
// (e.g. -1 == the last entry)
template<int N, bool IsPositive, typename...Args>
struct extract_nth_impl;
template<typename Arg0, typename...Args>
struct extract_nth_impl<0,true,Arg0,Args...> {
using type = Arg0;
};
template<int N, typename Arg0, typename...Args>
struct extract_nth_impl<N,true,Arg0,Args...>
: extract_nth_impl<N-1,true,Args...>{};
template<int N, typename...Args>
struct extract_nth_impl<N,false,Args...> {
using type = typename extract_nth_impl<(sizeof...(Args)+N),true,Args...>::type;
};
// A type-trait wrapper to avoid the need for 'typename'
template<int N, typename...Args>
using extract_nth_t = typename extract_nth_impl<N,(N>=0),Args...>::type;
我们可以使用它来提取最后一个参数以确保它是int*,并使用倒数第二个参数来知道它的类型(T*)。然后我们就可以直接使用std::enable_if SFINAE- away 任何错误的输入,这样这个函数如果被误用就会编译失败。
template<
typename...Args,
typename...UArgs,
typename=std::enable_if_t<
(sizeof...(Args) >= 2) &&
(sizeof...(Args)-2)==(sizeof...(UArgs)) &&
std::is_same_v<extract_nth_t<-1,Args...>,int*> &&
std::is_pointer_v<extract_nth_t<-2,Args...>>
>
>
void foo(void(*func)(Args...), UArgs&&...params)
{
// your code here, e.g.:
// bar(func, std::forward<UArgs>(params)...);
}
注意:模板和签名有以下变化:
- 我们现在有
Args... 和 UArgs...。这是因为我们想要为func 捕获N 个参数类型,但我们只想要params 的N-2 参数
- 我们现在匹配
void(*func)(Args...) 而不是void(*func)(Args...,T*,int*)。 T* 不再是 template 参数
- 我们有这么长的
std::enable_if_t,它用于 SFINAE 消除不良情况,例如 N<2,签名参数数量的参数过多,T*(倒数第二个参数)不是指针,以及最后一个签名 arg 是 int*
但总的来说这是可行的。如果函数定义中需要T,您可以使用以下命令轻松提取:
using T = std::remove_point_t<extract_nth_t<-2,Args...>>;
(注意:remove_pointer_t 只匹配类型,不匹配指针)
以下测试用例适用于我使用clang-8.0 和-std=c++17:
void example1(bool, char*, int*){}
void example2(bool, int*, int*){}
void example3(char*, int*){}
void example4(char*, char*){}
int main() {
foo(&::example1,false);
// foo(&::example1); -- fails to compile - too few arguments (correct)
foo(&::example2,false);
// foo(&::example2,false,5); -- fails to compile - too many arguments (correct)
foo(&::example3);
// foo(&::example4); -- fails to compile - last argument is not int* (correct)
}
编辑:正如@max66 所指出的,此解决方案不会将可转换类型限制为param 的输入。这确实意味着如果任何param 不能正确转换,它可能会失败。如果这是 API 的重要质量属性,则可以向 std::enable_if 添加单独的条件来纠正此问题。