我想编写一个模板函数,该函数接受一个函数指针以及该函数的最后两个参数之外的所有参数。像这样:
template <typename T, typename... ARGS>
void foo(void(*func)(ARGS..., T*, int*), ARGS... params);
我想做以下事情:
void bar(bool, char*, int*)
的功能,我要调用:foo(bar, false)
void bar(bool, int*, int*)
的功能,我要调用:foo(bar, false)
void bar(char*, int*)
的功能,我要调用:foo(bar)
但是当我尝试像这样定义foo
时,我得到了错误:
错误C2672:
foo
:未找到匹配的重载函数
错误C2784:void foo(void (__cdecl *)(ARGS...,T* ,int *),ARGS...)
:无法从void (__cdecl* )(ARGS...,T *,int* )
推论void (bool,char *,int* )
的模板参数
我可以做些什么来帮助扣除?
答案 0 :(得分:2)
之所以不起作用,是因为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处理,以消除任何错误的输入,这样,如果滥用该函数将无法编译。
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个参数类型,但是我们只希望N-2
的{{1}}个参数params
而不是void(*func)(Args...)
。 void(*func)(Args...,T*,int*)
不再是T*
参数template
,它用于SFINAE消除不良情况,例如std::enable_if_t
,对于签名args的数量N<2
(倒数第二个参数)的参数太多不是指针,而最后一个签名arg是T*
但是总的来说这可行。如果函数的定义中需要int*
,则可以使用以下命令轻松提取它:
T
(注意: using T = std::remove_point_t<extract_nth_t<-2,Args...>>;
仅用于匹配类型,而不是指针)
以下使用remove_pointer_t
和clang-8.0
的测试用例对我有用:
-std=c++17
编辑:如@ max66所指出,此解决方案不限制可转换类型作为对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)
}
的输入。这确实意味着如果任何param
无法正确转换,它可能会失败。如果这是API的重要质量属性,则可以为param
添加一个单独的条件来纠正此问题。
答案 1 :(得分:1)
在我看来,您正在寻找如下内容
template <std::size_t N, typename ...Ts>
using revType = std::tuple_element_t<sizeof...(Ts)-1u-N, std::tuple<Ts...>>;
template <typename ... As1, typename ... As2,
typename T = revType<1u, As1...>,
std::enable_if_t<std::is_same_v<
std::tuple<As1...>, std::tuple<As2..., T, int*>>, int> = 0>
void foo(void(*f)(As1...), As2 ... as)
{
}
正如Bitwize指出的(感谢),此解决方案有很大的局限性:要求将foo()
(类型为As2...
)的参数精确地推导为相应的类型As1...
。因此,如果As1...
以std::string
开头,则不能将"abc"
作为第一个As2...
参数,因为"abc"
是可转换的char const [4]
但与std::string
不同。
正如Bitwize本身所建议的那样,与利用std::is_convertible
转换构造函数的元组相比,您可以使用std::is_same_v
而不是std::tuple
来避免此问题,因此
template <typename ... As1, typename ... As2,
typename T = revType<1u, As1...>,
std::enable_if_t<std::is_convertible_v<
std::tuple<As1...>, std::tuple<As2..., T, int*>>, int> = 0>
void foo(void(*f)(As1...), As2 ... as)
{
}
在这种情况下,如果您要确保最后一个As1...
类型是完全 int *
(不仅是可转换的),可以添加支票>
template <typename ... As1, typename ... As2,
typename T = revType<1u, As1...>,
typename U = revType<0u, As1...>,
std::enable_if_t<
std::is_convertible_v<std::tuple<As1...>,
std::tuple<As2..., T, int*>>
&& std::is_same_v<int *, U>, int> = 0>
void foo(void(*f)(As1...), As2 ... as)
{
}