【问题标题】:DescribeFunction Parameters With Variadic Template Types具有可变模板类型的 DescribeFunction 参数
【发布时间】:2019-05-30 14:55:04
【问题描述】:

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

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 时,我得到了错误:

错误 C2672:foo:找不到匹配的重载函数
错误 C2784: void foo(void (__cdecl *)(ARGS...,T* ,int *),ARGS...): 无法从 void (bool,char *,int* ) 推断出 void (__cdecl* )(ARGS...,T *,int* ) 的模板参数

我可以做些什么来帮助扣除?

【问题讨论】:

  • 改用template &lt;typename Func, typename... ARGS&gt; void foo(Func func, ARGS... params);
  • @NathanOliver 我实际上是想基于T 进行专门化...所以我真的不能只将函数指针类型作为模板参数。
  • 演绎中使用的可变参数包只能用在签名的末尾——因此从这个角度解决问题是行不通的。您可能需要推导出整个签名,并使用 SFINAE 来约束允许的参数,并根据最后两个参数的推导来调度功能。
  • @Bitwize 如果你能告诉我如何建立这一点,这样我就可以根据T 进行专业化,这绝对值得我接受和支持:)
  • @JonathanMee 我现在正在工作,但会尽量在午休时间找时间。您正在编译哪个版本的 C++?

标签: c++ templates function-pointers variadic-templates variadic


【解决方案1】:

这不起作用的原因是因为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)...);
}

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

  1. 我们现在有Args... UArgs...。这是因为我们想要为func 捕获N 个参数类型,但我们只想要paramsN-2 参数
  2. 我们现在匹配void(*func)(Args...) 而不是void(*func)(Args...,T*,int*)T* 不再是 template 参数
  3. 我们有这么长的std::enable_if_t,它用于 SFINAE 消除不良情况,例如 N&lt;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 添加单独的条件来纠正此问题。

【讨论】:

    【解决方案2】:

    在我看来,您正在寻找如下内容

    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)
     {
     }
    

    【讨论】:

    • @JonathanMee - 显然你可以在T上添加约束
    • 与元组相比,这非常漂亮——尽管要考虑的一件事是,这限制了 As2 的可变参数包可以推断为的类型。如果As1short 作为附加参数,而As2 从文字推导出int - 这将使SFINAE 失败,因为tuple 不一样
    • @Bitwize - 是的:不幸的是,这是这个解决方案的一个限制。使用 C++17 很容易绕过这个限制(std::is_convertible 加上模板折叠......也许我会添加一个例子)但在 C++14 中需要一些繁重的样板(我想);如果f()paramsfoo() 被调用(我认为这是一个合理的假设),也许你的解决方案没问题(根本不检查UArgs... 的对应关系)。
    • @max66 我只是在我的解决方案中省略了它,因为我没有使用std::tuple,并且不想编写一个完整的单独类型特征来解决它。使用std::tuple,我认为您可能可以使用std::is_constructible 而不是std::is_same 来满足这一点,因为std::tuple 具有转换构造函数——尽管这需要明确检查最后一个类型是否仍然是int*
    • @Bitwize - 我没有考虑 std::tuple 转换构造函数;这也解决了 C++14 中的问题;谢谢。而且,是的:在这种情况下,您还必须检查最后一个 As1... 是否完全 int *。考虑到这一点,我概括了getPenultimate 类型特征。 (关于这个......在您的解决方案中,使用std::tuple_element_t 而不是开发递归extract_nth_impl 是不是更简单?)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-04-25
    • 2015-08-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多