C++中如何定义相同类型的可变参数(一)

您所在的位置:网站首页 csgov2参数放哪 C++中如何定义相同类型的可变参数(一)

C++中如何定义相同类型的可变参数(一)

2023-03-30 22:06| 来源: 网络整理| 查看: 265

从 C++98 开始,随着模板的出现,函数参数可以接受任意类型:

template void myFunction(T const& x) // T can be of any type { // ... }

在 C++11 中,增加了可变参数模板的功能,允许函数接受任意数量的任意类型参数:

template void myFunction(Ts const&... xs) // the Ts can be of any number of any type { // ... }

那么我们如何让一个函数接受任意数量的相同类型的参数呢?这也许在某些特定场景的业务代码中有用。

让我们看一个有这种需求的案例,以及实现它的3种方法:

SFINAEstatic_assertC++中鲜为人知的特性场景:将输入分成多个部分

让我们设计一个可以用任意数量的字符串调用的函数:

f("So"); f("So", "long"); f("So", "long", ", and thanks for all the fish");

我们有几个字符串,每个字符串都是不同的来源。我们想将它们全部交给f,而无需将它们全部组装起来。

另外,我们不想f 接受不是(可转换为)字符串的值。例如,我们希望以下代码无法通过编译:

f("So", 42, "long");

因为第二个参数 int 无法自动转换为字符串。

要实现f,我们不能只是将"..."可变参数运算符粘贴到std::string的后面:

void myFunction(std::string const&... xs) // imaginary C++! { // ... }

那么,我们如何实现f函数呢?

解决方案1:SFINAE

使用 SFINAE 禁用参数类型无法转换为std::string的模板实例化。

为此,我们需要确定两件事:

enable_if中如何表示表达式的类型是(可转换为)字符串;在函数原型中enable_if该放在什么位置处。类型是(可转换为)std::string

要检查一个给定类型是否可转换为 std::string,我们可以使用,在 C++11 的type_traits头文件中提供的is_convertible类型特征:

std::is_convertible::value

现在我们可以检查每个参数类型是否是一个字符串,那么我们如何检查所有参数类型呢?

在 C++17 中,我们可以使用std::conjunction(更直接的使用std::conjunction_v)模板:

std::conjunction_v

或者我们可以使用折叠表达式:

std::is_convertible_v && ...

如果你无法使用 C++17,可以自己在 C++11 中模拟实现一个std::conjunction,可以通过继承的方式递归地遍历可变参数包来实现:

template struct conjunction : std::true_type { }; template struct conjunction : B1 { }; template struct conjunction : std::conditional::type {};

还有一种通过判定两个类型是否相同的方式来实现(很巧妙的思路,避免了递归继承):

template struct bool_pack{}; template using conjunction = std::is_same;

现在我们可以在 C++11 中表达可变参数包只包含可转换为std::string的类型:

conjunction::value

要使用 SFINAE,我们需要将此表达式放在std::enable_if中:

std::enable_if::type;

为了使 SFINAE 看起来漂亮一些,我们可以为其定义一个别名模板:

template using AllStrings = typename std::enable_if::type; enable_if放在函数的哪里

让我们再看看可变参数模板函数:

template void f(Ts const&... xs) { // ... }

我们在哪里插入 SFINAE 表达式呢?为了使 SFINAE 看起来漂亮,一个好的选择通常是使用默认模板参数:

template void f(Ts const&... xs) { // ... }

但是可变参数类型不应该是模板参数列表中的最后一个参数吗?它后面还可以有一个默认参数吗?事实证明,只要包中的参数类型是被推到出来的,就可以存在,就是我们这里的情况。

我们可以对每个参数进行 SFINAE 吗?

在上面的接口中,它是一个全局的模板参数,携带着各个函数参数的信息。然而,携带该信息的不应该是参数本身吗?难道我们不能像这样编写一个接口:

template void f(std::enable_if_t const&... ts) { // ... }

好吧,我们可以这样写一个接口。但问题是我们不能通过传递std::string来直接调用它:

f(std::string("hello"), std::string("world")); // oops, no conversion from // string to enable_if_t

你可能认为enable_if_t其实就是 std::string,但是编译器在做类型推导并尝试实例化函数时并不清楚这些。

参考

https://www.fluentcpp.com/2019/01/25/variadic-number-function-parameters-type/

本文使用 Zhihu On VSCode 创作并发布



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3