具有可变模板类型的 DescribeFunction 参数
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
时,我得到了错误:
error C2672: foo
: no matching overloaded function found
error C2784: void foo(void (__cdecl *)(ARGS...,T* ,int *),ARGS...)
: could not deduce template argument for void (__cdecl* )(ARGS...,T *,int* )
from void (bool,char *,int* )
我可以做些什么来帮助扣除?
在我看来,您正在寻找如下内容
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...
类型)的参数推导exactly 作为 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)
{
}
这不起作用的原因是因为 ARGS...
是一个可变参数包,当用于推导时,它只能用于函数签名的 end .例如,您可以推断:
void(*)(int,Args...)
但是你无法推断
void(*)(Args...,int)
由于您的问题需要 last 参数是特定类型,并且要具体推导 second-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*
,并使用 second-last 参数来确定它的类型 (T*
)。那么我们可以直接用std::enable_if
到SFINAE-away任意bad-inputs,这样这个函数如果用错了就会编译失败
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*
(second-last 参数)不是指针,最后一个签名 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 <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
时,我得到了错误:
error C2672:
foo
: no matching overloaded function found
error C2784:void foo(void (__cdecl *)(ARGS...,T* ,int *),ARGS...)
: could not deduce template argument forvoid (__cdecl* )(ARGS...,T *,int* )
fromvoid (bool,char *,int* )
我可以做些什么来帮助扣除?
在我看来,您正在寻找如下内容
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...
类型)的参数推导exactly 作为 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)
{
}
这不起作用的原因是因为 ARGS...
是一个可变参数包,当用于推导时,它只能用于函数签名的 end .例如,您可以推断:
void(*)(int,Args...)
但是你无法推断
void(*)(Args...,int)
由于您的问题需要 last 参数是特定类型,并且要具体推导 second-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*
,并使用 second-last 参数来确定它的类型 (T*
)。那么我们可以直接用std::enable_if
到SFINAE-away任意bad-inputs,这样这个函数如果用错了就会编译失败
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
的 - 我们现在匹配
void(*func)(Args...)
而不是void(*func)(Args...,T*,int*)
。T*
不再是template
参数 - 我们有这么长的
std::enable_if_t
用于 SFINAE 消除不良情况,例如N<2
,签名参数数量的参数太多,T*
(second-last 参数)不是指针,最后一个签名 arg 是int*
N-2
个参数
但总的来说这是有效的。如果函数定义中需要 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
添加一个单独的条件来纠正此问题