具有可变模板类型的 DescribeFunction 参数

DescribeFunction Parameters With Variadic Template Types

我想编写一个模板函数,它接收函数指针和该函数的最后两个参数以外的所有参数。像这样:

template <typename T, typename... ARGS>
void foo(void(*func)(ARGS..., T*, int*), ARGS... params);

我想做如下事情:

  1. 鉴于签名void bar(bool, char*, int*)的功能,我想调用:foo(bar, false)
  2. 鉴于签名void bar(bool, int*, int*)的功能,我想调用:foo(bar, false)
  3. 给定签名函数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)...);
}

注意:模板和签名有以下变化:

  1. 我们现在有 Args... UArgs...。这是因为我们想为 func 捕获 N 个参数类型,但我们只想要 params
  2. N-2 个参数
  3. 我们现在匹配 void(*func)(Args...) 而不是 void(*func)(Args...,T*,int*)T* 不再是 template 参数
  4. 我们有这么长的 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 添加一个单独的条件来纠正此问题