c++ - 如何在没有辅助函数模板的情况下检索可变参数模板参数?

标签 c++ templates variadic-templates

假设我有

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++ 不会推导出查看函数内部的模板参数,而只会推导出参数和返回类型)

我发现的唯一方法是使用另一个函数来找出 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...使用模板。

我真的很想在 中做到这一点单功能 .每次我想检索参数包时,我真的必须定义辅助函数吗?

编辑:IntsMyFunc只是玩具的例子。我想知道检索模板参数的一般方法!

最佳答案

理想的界面是什么样的?

如果给定类型为 Ints<S...> 的变量,理想情况下,我们可以使用 S...尽可能少的修改。

在这种情况下,我们可以设计一个接口(interface),允许我们将参数包用作可变参数函数或 lambda 的输入,甚至可以将这些值重用为模板参数。

建议的接口(interface)[动态案例/作为值传递的整数]

静态案例和动态案例都有类似的接口(interface),但动态案例稍微干净一些,并且更好地介绍。给定变量和函数,我们将函数与包含在变量定义中的参数包一起应用。

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并将其用作参数包。

编写这个接口(interface)

这部分也很简单。我们采用 Ints<S...> 类型的变量, 和一个函数,并用 S... 应用该函数.
template<int... S, class Func>
auto operator|(Ints<S...>, Func&& f) {
    return f(S...); 
}

建议的接口(interface) [静态案例/可用作模板参数的整数]

如前所述,静态案例与动态案例具有相似的接口(interface),从概念上讲不会有太大的牵连。从用户的角度来看,唯一的区别是不是使用 S...作为参数包,我们ll use S.value...` 作为包。

对于每个值,我们希望将其封装在以该值为模板的相应类型中。这允许我们在 constexpr 上下文中访问它。
template<int Value>
struct ConstInt {
    constexpr static int value = Value;
};

为了将其与动态情况区分开来,我将重载 /而不是 | .否则,它们的行为相似。除了值被包裹在 ConstInt 中之外,实现与动态情况几乎相同。类,每个类都有自己的类型。
template<int... S, class F>
auto operator/(Ints<S...>, F&& func) {
    return func(ConstInt<S>()...); 
}

静态使用这个接口(interface)

C++ 允许我们使用与非静态成员相同的语法访问类的静态成员,而不会丢失 constexpr地位。

假设我有一些 ConstInt值为 10。我可以直接使用 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)...);
};

在这个例子中,我们对序列中的每个元素进行平方,并返回一个新序列:
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 ? 这个函数接受一堆值,它返回一个函数,该函数接受另一个函数并将该函数以 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 + ...); }); 

附录

本附录简要概述了如何更普遍地使用此语法(例如在使用多个单独的参数包时)。

奖励示例:配对来自两个单独包的值

为了让您更好地了解此接口(interface)的灵活性,这里有一个示例,我们使用它来配对来自两个单独包的值。
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}... }; 
    };
};

注意: MSVC 和 GCC 都可以毫无问题地编译此示例,但是 clang 对此感到窒息。我认为 MSVC 和 GCC 是正确的,但我不确定。

奖励示例:获取二维时间表

这个例子有点复杂,但我们也可以创建二维值数组,这些数组从单独包的所有值组合中提取。

在这种情况下,我使用它来创建时间表。
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++ - 如何在没有辅助函数模板的情况下检索可变参数模板参数?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55946950/

相关文章:

C++ 方法名称作为模板参数

c++ - 可变参数模板模板

c++ - 如何将来自用户定义文字的可变字符模板参数转换回数字类型?

c++ - 可变参数 lambda 捕获的解决方法

c++ - 检测SD卡硬件盘符

c++ - 如何使用 std::conditional 选择一个自由函数

c# - 套接字代理服务器

python - Django 将多个模型传递给模板

c++ - 删除在另一个函数中分配的内存?

C++ 聚合关系创建