[英]DescribeFunction Parameters With Variadic Template Types
我想编写一个模板函数,该函数需要一个函数指针以及该函数的最后两个参数以外的所有参数。 像这样:
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...)
__cdeclvoid foo(void (__cdecl *)(ARGS...,T* ,int *),ARGS...)
:无法推断出void (__cdecl* )(ARGS...,T *,int* )
从void (bool,char *,int* )
我可以做些什么来帮助扣除?
之所以不起作用,是因为ARGS...
是一个可变参数包,当用于推导时,它只能在函数签名的末尾使用。 例如,您可以推断:
void(*)(int,Args...)
但是你不能推论
void(*)(Args...,int)
由于您的问题要求最后一个参数为特定类型,而第二个参数则为第二个,因此您需要推导出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个参数类型,但是我们只想为params
捕获N-2
参数 void(*func)(Args...)
而不是void(*func)(Args...,T*,int*)
。 T*
不再是template
参数 std::enable_if_t
,它用于SFINAE消除不良情况,例如N<2
,对于签名args的数量来说参数太多, 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
添加单独的条件来纠正此问题。
在我看来,您正在寻找如下内容
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"
是可转换但与std::string
不同的char const [4]
。
正如Bitwize本身所建议的那样,您可以使用std::is_convertible
而不是std::is_same_v
来避免此问题,而不是使用std::tuple
转换构造函数的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)
{
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.