C++ 非类型参数包扩展
c++ non-type parameter pack expansion
我正在编写由单一类型参数化的模板函数,并且具有可变数量的相同类型(不是不同类型)的参数。它应该检查第一个值是否在其余值中。我想这样写:
#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 风格的可变参数函数结束。
如何编写接受任意数量的相同类型参数的声明?
我在这里看到两个选项。您可以传递一个 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... */
}
这允许调用原始代码段中的函数。与上面的解决方案相比,这个签名显然有两个模板参数,如果 S
不同于 T
.
,这可能需要再看一遍才能看到它会失败
首先,定义一个C风格的可变参数函数
static bool value_in (T val, T vals, ...)
...
前的逗号是可选的。
所以你的
static bool value_in(T val, T vals...)
定义两个非可变参数(val
和 vals
)和一个未命名的可变序列。
How to write declaration that will accept arbitrary number of parameters of the same type?
有很多方法,但是,恕我直言,有缺点
一种可能的方法是使用 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");
因为 T
、std::string
不同于 Ts...
、char const *
,并且 SFINAE 不启用 value_in
。
您可以使用 std::is_convertible
而不是 std::is_same
,但我建议另一种方法,分两步进行。
首先你需要一个自定义类型特征(使用 using
帮助器)到 select 列表中的第一个类型
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 兼容。
缺点:需要第二步。
优点(恕我直言):此解决方案通过声明接收 T
类型的第二步函数,因此也接受可转换为 T
.
的参数
即:这个解决方案也接受
value_in(std::string{"abc"}, "123");
因为不再需要 "123"
正好是 std::string
;也可以转换为 std::string
.
我正在编写由单一类型参数化的模板函数,并且具有可变数量的相同类型(不是不同类型)的参数。它应该检查第一个值是否在其余值中。我想这样写:
#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 风格的可变参数函数结束。
如何编写接受任意数量的相同类型参数的声明?
我在这里看到两个选项。您可以传递一个 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... */
}
这允许调用原始代码段中的函数。与上面的解决方案相比,这个签名显然有两个模板参数,如果 S
不同于 T
.
首先,定义一个C风格的可变参数函数
static bool value_in (T val, T vals, ...)
...
前的逗号是可选的。
所以你的
static bool value_in(T val, T vals...)
定义两个非可变参数(val
和 vals
)和一个未命名的可变序列。
How to write declaration that will accept arbitrary number of parameters of the same type?
有很多方法,但是,恕我直言,有缺点
一种可能的方法是使用 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");
因为 T
、std::string
不同于 Ts...
、char const *
,并且 SFINAE 不启用 value_in
。
您可以使用 std::is_convertible
而不是 std::is_same
,但我建议另一种方法,分两步进行。
首先你需要一个自定义类型特征(使用 using
帮助器)到 select 列表中的第一个类型
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 兼容。
缺点:需要第二步。
优点(恕我直言):此解决方案通过声明接收 T
类型的第二步函数,因此也接受可转换为 T
.
即:这个解决方案也接受
value_in(std::string{"abc"}, "123");
因为不再需要 "123"
正好是 std::string
;也可以转换为 std::string
.