c++非类型参数包扩展

c++ non-type parameter pack expansion

本文关键字:扩展 包扩展 类型参数 c++      更新时间:2023-10-16

我正在编写一个由单一类型参数化的模板函数,该函数具有不同数量的相同类型(而不是不同类型(的参数。它应该检查第一个值是否在其他值中。我想这样写:

#include <unordered_set>
template <typename T>
static bool value_in(T val, T vals...) {
// compiles, but uses only vals[0]:
const std::unordered_set<T> allowed {vals};
// error: pack expansion does not contain any unexpanded parameter packs:
// const std::unordered_set<T> allowed {vals...};
return allowed.find(val) != allowed.end();
}
// usage
enum class Enumeration {one, two, three};
int main () {
// should return true -> 0
return value_in(Enumeration::two,
Enumeration::one,
Enumeration::two) ? 0 : 1;
}

我本以为第二个会起作用,但它没有编译,因为

test.cpp: In function ‘bool value_in(T, T, ...)’:
test.cpp:7:46: error: expansion pattern ‘vals’ contains no argument packs

我看到的是"(T, T, ...)"而不是"(T, T...)",所以我可能把函数声明搞砸了,以C风格的变分函数结束。

如何编写接受任意数量的相同类型的参数的声明?

首先,定义一个C风格的变分函数

static bool value_in (T val, T vals, ...)

CCD_ 3之前的逗号是可选的。

所以你的

static bool value_in(T val, T vals...)

定义两个非可变参数(valvals(和一个未命名的可变序列。

如何编写接受任意数量相同类型参数的声明?

有很多方法,但IMHO有缺点

一种可能的方法是使用SFINAE:您可以强制要求可变类型等于第一种类型。

以下是使用模板折叠的C++17可能的解决方案

template <typename T, typename ... Ts>
std::enable_if_t<(std::is_same<T, Ts>::value && ...), bool>
value_in (T val, Ts ... vals) 
{
const std::unordered_set<T> allowed {val, vals ... };
return allowed.find(val) != allowed.end();
}

您也可以在C++11/C++14中开发此解决方案,但稍微复杂一些。

缺点:推导出了Ts...类型,它们必须是完全相同的T类型。

因此,例如,如果你想要一个接受std::string()列表的函数,你就不能用char const *来调用它

value_in(std::string{"abc"}, "123");

因为Tstd::string不同于Ts...char const *,并且SFINAE不启用value_in

您可以使用std::is_convertible而不是std::is_same,但我建议使用另一种方法,分两步。

首先,您需要一个自定义类型traits(使用using助手(来从列表中选择第一个类型

template <typename T, typename ...>
struct firstType
{ using type = T; };
template <typename T, typename ... Ts>
using firstType_t = typename firstType<T, Ts...>::type;

现在,您可以编写第一步value_in(),截取所有值,检测所有类型(无限制(,并将它们传递给第二步函数,如下所示

template <typename T, typename ... Ts>
bool value_in (T val, Ts ... vals) 
{ return value_in_helper<T, Ts...>(val, vals...); }

第二步功能使用firstType更改T中的所有Ts...类型

template <typename T, typename ... Ts>
bool value_in_helper (T val, firstType_t<T, Ts> ... vals) 
{
const std::unordered_set<T> allowed {val, vals ... };
return allowed.find(val) != allowed.end();
}

此解决方案与C++11兼容。

缺点:你需要第二步。

优点(IMHO(:此解决方案通过第二步函数,该函数声明接收T类型,因此也接受可转换为T的参数。

也就是说:这个解决方案也接受

value_in(std::string{"abc"}, "123");

因为不再需要CCD_ 24恰好是CCD_;也可以转换为CCD_ 26。

我在这里看到了两个选项。您可以传递std::initializer_list,这将导致函数签名更改为

#include <initializer_list>
template <typename T>
static bool value_in(T&& val, std::initializer_list<T> vals)
{
/* Implementation as before */
}

以及的调用片段

return value_in(Enumeration::two,
{ Enumeration::one, Enumeration::two }) ? 0 : 1;

注意这里的附加大括号,它们是构造要传递的初始值设定项列表所必需的。这种方法的一个小细节是函数签名,它立即表明只有一种类型可以推导。

如果你觉得打牙套不对,坚持你最初的尝试,调整你的功能,这样

template <typename S, typename... T>
static bool value_in(S&& val, T&&... vals) {
const std::unordered_set<S> allowed {std::forward<T>(vals)...};
/* As before... */
}

这允许像在原始代码段中那样调用函数。与上面的解决方案相反,这个签名显然有两个模板参数,这可能需要再次查看,如果ST不同,它将失败。