如何在没有辅助函数模板的情况下检索可变参数模板参数?
How to retrieve variadic template parameters without auxillary function template?
假设我有
template<int ...>
struct Ints { };
class MyClass
{
public:
Ints<1, 2, 3> get() { return Ints<1, 2, 3>(); }
};
我想做的很简单
template <class T>
vector<int> MyFunc1(T& x)
{
Ints<S...> result = x.get();
vector<int> t = { S... };
return t;
}
有点像这样。 (这里的MyClass
可以是T
的一个例子。)显然,对于编译器来说S...
似乎是无效的。
template <class T, int... S>
vector<int> MyFunc2(T& x)
{
Ints<S...> result = x.get();
vector<int> t = { S... };
return t;
}
这也不行。我认为从 get()
开始,S...
变得具体且可自动推导,但编译器无法识别它。 (我不确定,但 C++ 不会在函数内部推导模板参数,而只会推导参数和 return 类型)
我发现的唯一方法是使用另一个函数找出 int...
是什么。
template <int ...S>
vector<int> temp(Ints<S...> not_used)
{
return { S... };
}
template <class T>
vector<int> MyFunc3(T& x)
{
auto result = x.get();
return temp(result);
}
它运行良好,但需要另一个额外的辅助函数,它什么都不做,只是提供语法清晰的方式来匹配 S...
使用模板。
我真的很想在单一功能中做到这一点。每当我想要检索参数包时,我真的必须定义辅助函数吗?
编辑:Ints
和 MyFunc
只是玩具示例。我想知道获取模板参数的一般方法!
我建议向 struct Int
添加函数以获得不同的表示
#include <vector>
#include <array>
template<int ...values>
struct Ints {
auto getAsVector() const {
return std::vector<int>({ values... });
}
constexpr auto getAsArray() const {
return std::array<int, sizeof...(values)>({ values... });
}
};
class MyClass
{
public:
Ints<1, 2, 3> get() { return Ints<1, 2, 3>(); }
};
int main() {
MyClass a;
auto array = a.get().getAsVector();
return array.size();
}
如果您没有 use/create 辅助模板,则需要其他方式 来提供值。
我能想到的最简单、规范和通用的方法是将它们放在相同的 class 范围内,这样您的 Ints
结构就变成了:
template<int ...ints>
struct Ints {
constexpr static std::initializer_list<int> vals = {ints...};
};
因为它是 constexpr
,所以应该在编译时对其进行评估,而不是产生运行时成本。
现在您可以执行以下操作:
return std::vector<int>(ints.vals);
理想的界面是什么样的?
如果给定一个 Ints<S...>
类型的变量,我们最好能够使用 S...
并尽可能少地修改。
在这种情况下,我们可以设计一个接口,允许我们将参数包用作可变参数函数或 lambda 的输入,甚至可以将这些值重用为模板参数。
提议的接口[动态大小写/整数作为值传递]
静态案例和动态案例都有相似的界面,但是动态案例稍微干净一些,并且有更好的介绍。给定变量和函数,我们使用变量定义中包含的参数包应用函数。
Ints<1, 2, 3> ints;
// Get a vector from ints
// vec = {1, 2, 3}
auto vec = ints | [](auto... S) { return std::vector {S...}; };
// Get an array from ints
// arr = {1, 2, 3}
auto arr = ints | [](auto... S) { return std::array {S...}; };
// Get a tuple from ints
// tup = {1, 2, 3}
auto tup = ints | [](auto... S) { return std::make_tuple(S...); };
// Get sum of ints using a fold expression
auto sum = ints | [](auto... S) { return (S + ...); };
这是一种简单、统一的语法,它允许我们采用 S
并将其用作参数包。
正在编写此接口
这部分也很简单。我们取一个 Ints<S...>
类型的变量和一个函数,并用 S...
应用该函数。
template<int... S, class Func>
auto operator|(Ints<S...>, Func&& f) {
return f(S...);
}
提议的接口[可用作模板参数的静态案例/整数]
如前所述,静态案例与动态案例具有相似的界面,并且在概念上不会有太大的延伸。从用户的角度来看,唯一的区别是我们不使用 S...
作为参数包,而是使用 ll use
S.value...` 作为参数包。
对于每个值,我们都希望将其封装在以该值为模板的相应类型中。这允许我们在 constexpr 上下文中访问它。
template<int Value>
struct ConstInt {
constexpr static int value = Value;
};
为了区别于动态情况,我将重载 /
而不是 |
。否则,它们的行为相似。实现与动态情况几乎相同,只是值被包装在 ConstInt
class 中,并且每个值都有自己的类型。
template<int... S, class F>
auto operator/(Ints<S...>, F&& func) {
return func(ConstInt<S>()...);
}
静态使用此接口
C++ 允许我们使用与非静态成员相同的语法访问 class 的静态成员,而不会丢失 constexpr
状态。
假设我有一些值为10的ConstInt
。我可以直接使用I.value
作为模板参数,或者我可以使用decltype(I)::value
:
// This is what'll be passed in as a parameter
ConstInt<10> I;
std::array<int, I.value> arr1;
std::array<int, decltype(I)::value> arr2;
// Both have length 10
因此扩展参数包非常简单,它最终与动态情况几乎相同,唯一的区别是 .value
附加到 S
。下面显示的是动态案例的示例,这次使用静态案例语法:
Ints<1, 2, 3> ints;
// Get a vector from ints
auto vec = ints | [](auto... S) { return std::vector {S.value...}; };
// Get an array from ints
// arr = {1, 2, 3}
auto arr = ints | [](auto... S) { return std::array {S.value...}; };
// Get a tuple from ints
auto tup = ints | [](auto... S) { return std::make_tuple(S.value...); };
// Get sum of ints using a fold expression
auto sum = ints | [](auto... S) { return (S.value + ...); };
有什么新鲜事吗? 因为 value
是 constexpr,S.value
可以简单地用作模板参数。 在这个例子中,我们使用 S.value
来索引元组使用std::get
:
auto tupA = std::make_tuple(10.0, "Hello", 3);
auto indicies = Ints<2, 0, 1>{};
// tupB = {3, 10.0, "Hello"}
auto tupB = indicies / [&](auto... S) {
return std::make_tuple(std::get<S.value>(tupA)...);
};
在这个例子中,我们对序列中的每个元素进行平方,return 一个新序列:
auto ints = Ints<0, 1, 2, 3, 4, 5>();
// ints_squared = Ints<0, 1, 4, 9, 16, 25>();
auto ints_squared = ints / [](auto... S) {
return Ints<(S.value * S.value)...>();
};
避免运算符重载的替代解决方案
如果你想避免运算符重载,我们可以从函数式编程中得到一些启发,用一个 unpack
函数来处理事情,写成这样:
template<int... vals>
auto unpack(Ints<vals...>) {
return [](auto&& f) { return f(vals...); };
}
// Static case
template<int... vals>
auto unpack_static(Ints<vals...>) {
return [](auto&& f) { return f(ConstInt<vals>()...); };
}
那么什么是unpack
?这个函数接受一堆值,它return是一个接受另一个函数并应用该函数的函数vals 作为输入。
unpack
函数允许我们将这些值作为参数应用于不同的函数。
我们可以将结果分配给一个名为 apply_ints
的变量,然后我们可以使用 apply_ints
来处理所有特定的用例:
Ints<1, 2, 3> ints; //this variable has our ints
auto apply_ints = unpack(ints); // We use this function to unpack them
我们可以重写之前的例子,这次使用apply_ints
:
// Get a vector from ints
// vec = {1, 2, 3}
auto vec = apply_ints([](auto... S) { return std::vector {S...}; });
// Get an array from ints
// arr = {1, 2, 3}
auto arr = apply_ints([](auto... S) { return std::array {S...}; });
// Get a tuple from ints
// tup = {1, 2, 3}
auto tup = apply_ints([](auto... S) { return std::make_tuple(S...); });
// Get sum of ints using a fold expression
auto sum = apply_ints([](auto... S) { return (S + ...); });
附录
本附录简要概述了如何更普遍地使用此语法(例如在使用多个单独的参数包时)。
奖励示例:将来自两个单独包的值配对
为了让您更好地了解此接口的灵活性,下面是一个示例,我们使用它来配对来自两个独立包的值。
Ints<1, 2, 3> intsA;
Ints<10, 20, 30> intsB;
// pairs = {{1, 10}, {2, 20}, {3, 30}}
auto pairs = intsA | [&](auto... S1) {
return intsB | [&](auto... S2) {
return std::vector{ std::pair{S1, S2}... };
};
};
NB: MSVC 和 GCC 都可以毫无问题地编译这个示例,但是 clang 会阻塞它。我假设 MSVC 和 GCC 是正确的,但我不确定。
奖励示例:获取二维时间 table
这个例子有点复杂,但我们也可以创建二维值数组,从不同包的所有值组合中提取值。
在这种情况下,我用它来创建一个时代table。
Ints<1, 2, 3, 4, 5, 6, 7, 8, 9> digits;
auto multiply = [](auto mul, auto... vals) {
return std::vector{(mul * vals)...};
};
auto times_table = digits | [&](auto... S1) {
return digits | [&](auto... S2) {
return std::vector{ multiply(S1, S2...)... };
};
};
在 C++2a 中,您可以使用模板化 lambda 在您的函数中定义您的助手,例如:
auto v = []<std::size_t...Is>(std::index_sequence<Is...>){return std::vector{Is...};}(seq);
// ^^^^^^^^^^^^^^^^^^ New in C++2a
假设我有
template<int ...>
struct Ints { };
class MyClass
{
public:
Ints<1, 2, 3> get() { return Ints<1, 2, 3>(); }
};
我想做的很简单
template <class T>
vector<int> MyFunc1(T& x)
{
Ints<S...> result = x.get();
vector<int> t = { S... };
return t;
}
有点像这样。 (这里的MyClass
可以是T
的一个例子。)显然,对于编译器来说S...
似乎是无效的。
template <class T, int... S>
vector<int> MyFunc2(T& x)
{
Ints<S...> result = x.get();
vector<int> t = { S... };
return t;
}
这也不行。我认为从 get()
开始,S...
变得具体且可自动推导,但编译器无法识别它。 (我不确定,但 C++ 不会在函数内部推导模板参数,而只会推导参数和 return 类型)
我发现的唯一方法是使用另一个函数找出 int...
是什么。
template <int ...S>
vector<int> temp(Ints<S...> not_used)
{
return { S... };
}
template <class T>
vector<int> MyFunc3(T& x)
{
auto result = x.get();
return temp(result);
}
它运行良好,但需要另一个额外的辅助函数,它什么都不做,只是提供语法清晰的方式来匹配 S...
使用模板。
我真的很想在单一功能中做到这一点。每当我想要检索参数包时,我真的必须定义辅助函数吗?
编辑:Ints
和 MyFunc
只是玩具示例。我想知道获取模板参数的一般方法!
我建议向 struct Int
添加函数以获得不同的表示
#include <vector>
#include <array>
template<int ...values>
struct Ints {
auto getAsVector() const {
return std::vector<int>({ values... });
}
constexpr auto getAsArray() const {
return std::array<int, sizeof...(values)>({ values... });
}
};
class MyClass
{
public:
Ints<1, 2, 3> get() { return Ints<1, 2, 3>(); }
};
int main() {
MyClass a;
auto array = a.get().getAsVector();
return array.size();
}
如果您没有 use/create 辅助模板,则需要其他方式 来提供值。
我能想到的最简单、规范和通用的方法是将它们放在相同的 class 范围内,这样您的 Ints
结构就变成了:
template<int ...ints>
struct Ints {
constexpr static std::initializer_list<int> vals = {ints...};
};
因为它是 constexpr
,所以应该在编译时对其进行评估,而不是产生运行时成本。
现在您可以执行以下操作:
return std::vector<int>(ints.vals);
理想的界面是什么样的?
如果给定一个 Ints<S...>
类型的变量,我们最好能够使用 S...
并尽可能少地修改。
在这种情况下,我们可以设计一个接口,允许我们将参数包用作可变参数函数或 lambda 的输入,甚至可以将这些值重用为模板参数。
提议的接口[动态大小写/整数作为值传递]
静态案例和动态案例都有相似的界面,但是动态案例稍微干净一些,并且有更好的介绍。给定变量和函数,我们使用变量定义中包含的参数包应用函数。
Ints<1, 2, 3> ints;
// Get a vector from ints
// vec = {1, 2, 3}
auto vec = ints | [](auto... S) { return std::vector {S...}; };
// Get an array from ints
// arr = {1, 2, 3}
auto arr = ints | [](auto... S) { return std::array {S...}; };
// Get a tuple from ints
// tup = {1, 2, 3}
auto tup = ints | [](auto... S) { return std::make_tuple(S...); };
// Get sum of ints using a fold expression
auto sum = ints | [](auto... S) { return (S + ...); };
这是一种简单、统一的语法,它允许我们采用 S
并将其用作参数包。
正在编写此接口
这部分也很简单。我们取一个 Ints<S...>
类型的变量和一个函数,并用 S...
应用该函数。
template<int... S, class Func>
auto operator|(Ints<S...>, Func&& f) {
return f(S...);
}
提议的接口[可用作模板参数的静态案例/整数]
如前所述,静态案例与动态案例具有相似的界面,并且在概念上不会有太大的延伸。从用户的角度来看,唯一的区别是我们不使用 S...
作为参数包,而是使用 ll use
S.value...` 作为参数包。
对于每个值,我们都希望将其封装在以该值为模板的相应类型中。这允许我们在 constexpr 上下文中访问它。
template<int Value>
struct ConstInt {
constexpr static int value = Value;
};
为了区别于动态情况,我将重载 /
而不是 |
。否则,它们的行为相似。实现与动态情况几乎相同,只是值被包装在 ConstInt
class 中,并且每个值都有自己的类型。
template<int... S, class F>
auto operator/(Ints<S...>, F&& func) {
return func(ConstInt<S>()...);
}
静态使用此接口
C++ 允许我们使用与非静态成员相同的语法访问 class 的静态成员,而不会丢失 constexpr
状态。
假设我有一些值为10的ConstInt
。我可以直接使用I.value
作为模板参数,或者我可以使用decltype(I)::value
:
// This is what'll be passed in as a parameter
ConstInt<10> I;
std::array<int, I.value> arr1;
std::array<int, decltype(I)::value> arr2;
// Both have length 10
因此扩展参数包非常简单,它最终与动态情况几乎相同,唯一的区别是 .value
附加到 S
。下面显示的是动态案例的示例,这次使用静态案例语法:
Ints<1, 2, 3> ints;
// Get a vector from ints
auto vec = ints | [](auto... S) { return std::vector {S.value...}; };
// Get an array from ints
// arr = {1, 2, 3}
auto arr = ints | [](auto... S) { return std::array {S.value...}; };
// Get a tuple from ints
auto tup = ints | [](auto... S) { return std::make_tuple(S.value...); };
// Get sum of ints using a fold expression
auto sum = ints | [](auto... S) { return (S.value + ...); };
有什么新鲜事吗? 因为 value
是 constexpr,S.value
可以简单地用作模板参数。 在这个例子中,我们使用 S.value
来索引元组使用std::get
:
auto tupA = std::make_tuple(10.0, "Hello", 3);
auto indicies = Ints<2, 0, 1>{};
// tupB = {3, 10.0, "Hello"}
auto tupB = indicies / [&](auto... S) {
return std::make_tuple(std::get<S.value>(tupA)...);
};
在这个例子中,我们对序列中的每个元素进行平方,return 一个新序列:
auto ints = Ints<0, 1, 2, 3, 4, 5>();
// ints_squared = Ints<0, 1, 4, 9, 16, 25>();
auto ints_squared = ints / [](auto... S) {
return Ints<(S.value * S.value)...>();
};
避免运算符重载的替代解决方案
如果你想避免运算符重载,我们可以从函数式编程中得到一些启发,用一个 unpack
函数来处理事情,写成这样:
template<int... vals>
auto unpack(Ints<vals...>) {
return [](auto&& f) { return f(vals...); };
}
// Static case
template<int... vals>
auto unpack_static(Ints<vals...>) {
return [](auto&& f) { return f(ConstInt<vals>()...); };
}
那么什么是unpack
?这个函数接受一堆值,它return是一个接受另一个函数并应用该函数的函数vals 作为输入。
unpack
函数允许我们将这些值作为参数应用于不同的函数。
我们可以将结果分配给一个名为 apply_ints
的变量,然后我们可以使用 apply_ints
来处理所有特定的用例:
Ints<1, 2, 3> ints; //this variable has our ints
auto apply_ints = unpack(ints); // We use this function to unpack them
我们可以重写之前的例子,这次使用apply_ints
:
// Get a vector from ints
// vec = {1, 2, 3}
auto vec = apply_ints([](auto... S) { return std::vector {S...}; });
// Get an array from ints
// arr = {1, 2, 3}
auto arr = apply_ints([](auto... S) { return std::array {S...}; });
// Get a tuple from ints
// tup = {1, 2, 3}
auto tup = apply_ints([](auto... S) { return std::make_tuple(S...); });
// Get sum of ints using a fold expression
auto sum = apply_ints([](auto... S) { return (S + ...); });
附录
本附录简要概述了如何更普遍地使用此语法(例如在使用多个单独的参数包时)。
奖励示例:将来自两个单独包的值配对
为了让您更好地了解此接口的灵活性,下面是一个示例,我们使用它来配对来自两个独立包的值。
Ints<1, 2, 3> intsA;
Ints<10, 20, 30> intsB;
// pairs = {{1, 10}, {2, 20}, {3, 30}}
auto pairs = intsA | [&](auto... S1) {
return intsB | [&](auto... S2) {
return std::vector{ std::pair{S1, S2}... };
};
};
NB: MSVC 和 GCC 都可以毫无问题地编译这个示例,但是 clang 会阻塞它。我假设 MSVC 和 GCC 是正确的,但我不确定。
奖励示例:获取二维时间 table
这个例子有点复杂,但我们也可以创建二维值数组,从不同包的所有值组合中提取值。
在这种情况下,我用它来创建一个时代table。
Ints<1, 2, 3, 4, 5, 6, 7, 8, 9> digits;
auto multiply = [](auto mul, auto... vals) {
return std::vector{(mul * vals)...};
};
auto times_table = digits | [&](auto... S1) {
return digits | [&](auto... S2) {
return std::vector{ multiply(S1, S2...)... };
};
};
在 C++2a 中,您可以使用模板化 lambda 在您的函数中定义您的助手,例如:
auto v = []<std::size_t...Is>(std::index_sequence<Is...>){return std::vector{Is...};}(seq);
// ^^^^^^^^^^^^^^^^^^ New in C++2a