调用元组元素的常见方法

发布于 2025-02-13 00:37:12 字数 1543 浏览 1 评论 0原文

假设我有一个类型的元组t1,...,tn实现某些方法,apply()

我如何定义一个借助此元组和一些初始元素的函数,并在此元素上返回apply()的链式调用?

例如:

template <typename... Args, typename Input>
auto apply(std::tuple<Args...> const &tpl, Input x) {
  // return ???
}

// simple example

struct Sqr {
  static int apply(int x) { return x * x; }
};

enum class Choice {
  One,
  Two,
};

struct Choose {
  static int apply(Choice choice) {
    switch (choice) {
    case Choice::One:
      return 1;
    case Choice::Two:
      return 2;
    }
  }
};

void test() {
  auto tpl = std::tuple(Sqr{}, Choose{});
  assert(apply(tpl, Choice::One) == 1);
  assert(apply(tpl, Choice::Two) == 4);
}

我尝试使用 fold表达式,以及答案的变化: 模板元素 - 在每个元素上调用一个函数,但无法获得任何要编译的东西。

主要区别是我需要每个调用的结果作为下一个调用的输入。

具体而言,我尝试了以下操作,它失败了,因为它调用了每个参数的初始值:

template <typename... Args, typename Input>
auto apply(std::tuple<Args...> const &tpl, Input x) {
  return std::apply([&x](auto &&... args) {
    return (..., args.apply(x));
  }, tpl);
}

澄清和假设:

  • 我希望以特定顺序调用这些方法 - 最后一个 - 与
  • 每个元组参数的输入和输出类型未限制。唯一的假设是连续参数就相应类型一致。

Say I have a tuple of types T1,...,TN that implement some method, apply().

How do I define a function that takes this tuple and some initial element, and returns the chained call of apply() on this element?

For example:

template <typename... Args, typename Input>
auto apply(std::tuple<Args...> const &tpl, Input x) {
  // return ???
}

// simple example

struct Sqr {
  static int apply(int x) { return x * x; }
};

enum class Choice {
  One,
  Two,
};

struct Choose {
  static int apply(Choice choice) {
    switch (choice) {
    case Choice::One:
      return 1;
    case Choice::Two:
      return 2;
    }
  }
};

void test() {
  auto tpl = std::tuple(Sqr{}, Choose{});
  assert(apply(tpl, Choice::One) == 1);
  assert(apply(tpl, Choice::Two) == 4);
}

I tried to use fold expressions, and variations of answers from: Template tuple - calling a function on each element but couldn't get anything to compile.

The main difference is that I need each invocation's result as the input for the next one.

Concretely, I tried the following, which failed because it calls each argument with the initial value:

template <typename... Args, typename Input>
auto apply(std::tuple<Args...> const &tpl, Input x) {
  return std::apply([&x](auto &&... args) {
    return (..., args.apply(x));
  }, tpl);
}

Clarifications and assumptions:

  • I want the methods to be called in a specific order - last to first - similarly to mathematical function composition.
    (f * g)(x) := f(g(x))
    
  • The input and output types of each tuple argument are not constricted. The only assumption is that consecutive arguments agree on the corresponding types.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

失退 2025-02-20 00:37:12

C ++可能会有17种方法,但总有良好的老式部分特权递归。我们将制作一个代表您的递归算法的struct,然后我们将围绕该struct构建功能包装器,以帮助类型推理。首先,我们需要一些进口。

#include <tuple>
#include <utility>
#include <iostream> // Just for debugging later :)

这是我们的结构定义。

template <typename Input, typename... Ts>
struct ApplyOp;

不是很有趣。这是一种不完整的类型,但我们将提供专业化。与任何递归一样,我们需要一个基本情况和递归步骤。我们正在将元组元素引入(您可以将其视为类似折叠的操作),因此我们的基本情况是元组为空时。

template <typename Input>
struct ApplyOp<Input> {
  Input apply(Input x) {
    return x;
  }
};

在这种情况下,我们只返回x。计算完成。

现在,我们的递归步骤采用了可变数量的参数(至少一个),并调用.apply

template <typename Input, typename T, typename... Ts>
struct ApplyOp<Input, T, Ts...> {
  auto apply(Input x, const T& first, const Ts&... rest) {
    auto tail_op = ApplyOp<Input, Ts...>();
    return first.apply(tail_op.apply(x, rest...));
  }
};

tail_op是我们的递归调用。它实例化 版本的applyop。此代码中有两个应用调用。 first.apply在类型t中应用方法;这是您控制的方法来确定每个步骤发生的情况。 tail_op.apply是我们对 this 的另一个版本的递归调用...是。

请注意,我们尚未对元素说任何话。我们刚刚采用了variadic参数包。我们将使用 std :: integer_sequence (更具体地说,是std :: index_sequence)。基本上,我们要服用一个包含n元素的元组,然后将其转换为表单的一系列参数,

std::get<0>(tup), std::get<1>(tup), ..., std::get<N-1>(tup)

因此我们需要从0n-1 包容性(其中n-1是我们的 std :: tuple_size )。

template <typename Input, typename... Ts>
auto apply(const std::tuple<Ts...>& tpl, Input x) {
  using seq = std::make_index_sequence<std::tuple_size<std::tuple<Ts...>>::value>;
  // ???
}

那种复杂的类型别名正在构建我们的索引序列。我们采用元组的尺寸(std :: Tuple_size&lt; std :: Tuple&lt; ts ...&gt;&gt; :: value)并将其传递给std :: make_index_sequence ,这为我们提供了std :: index_sequence&lt; 0,1,2,...,n-1&gt;。现在,我们需要将该索引序列作为参数包。我们可以使用额外的间接层进行类型推理来做到这一点。

template <typename Input, typename... Ts, std::size_t... Is>
auto apply(const std::tuple<Ts...>& tpl, Input x, std::index_sequence<Is...>) {
  auto op = ApplyOp<Input, Ts...>();
  return op.apply(x, std::get<Is>(tpl)...);
}

template <typename Input, typename... Ts>
auto apply(const std::tuple<Ts...>& tpl, Input x) {
  using seq = std::make_index_sequence<std::tuple_size<std::tuple<Ts...>>::value>;
  return apply(tpl, x, seq());
}

第二个应用是外部用户打电话的一个。他们通过元组和输入值。然后,我们构建适当类型的std :: index_sequence,并将其传递给第一个apply,该使用该索引序列依次访问元组的每个元素。

完成,可运行的示例

There may be snazzier C++17 ways of doing it, but there is always good old-fashioned partially-specialized recursion. We'll make a struct that represents your recursive algorithm, and then we'll build a function wrapper around that struct to aid in type inference. First, we'll need some imports.

#include <tuple>
#include <utility>
#include <iostream> // Just for debugging later :)

Here's our structure definition.

template <typename Input, typename... Ts>
struct ApplyOp;

Not very interesting. It's an incomplete type, but we're going to provide specializations. As with any recursion, we need a base case and a recursive step. We're inducting on the tuple elements (you're right to think of this as a fold-like operation), so our base case is when the tuple is empty.

template <typename Input>
struct ApplyOp<Input> {
  Input apply(Input x) {
    return x;
  }
};

In this case, we just return x. Computation complete.

Now our recursive step takes a variable number of arguments (at least one) and invokes .apply.

template <typename Input, typename T, typename... Ts>
struct ApplyOp<Input, T, Ts...> {
  auto apply(Input x, const T& first, const Ts&... rest) {
    auto tail_op = ApplyOp<Input, Ts...>();
    return first.apply(tail_op.apply(x, rest...));
  }
};

The tail_op is our recursive call. It instantiates the next version of ApplyOp. There are two apply calls in this code. first.apply is the apply method in the type T; this is the method you control which determines what happens at each step. The tail_op.apply is our recursive call to either another version of this apply function or to the base case, depending on what Ts... is.

Note that we haven't said anything about tuples yet. We've just taken a variadic parameter pack. We're going to convert the tuple into a parameter pack using an std::integer_sequence (More specifically, an std::index_sequence). Basically, we want to take a tuple containing N elements and convert it to a sequence of parameters of the form

std::get<0>(tup), std::get<1>(tup), ..., std::get<N-1>(tup)

So we need to get an index sequence from 0 up to N-1 inclusive (where N-1 is our std::tuple_size).

template <typename Input, typename... Ts>
auto apply(const std::tuple<Ts...>& tpl, Input x) {
  using seq = std::make_index_sequence<std::tuple_size<std::tuple<Ts...>>::value>;
  // ???
}

That complicated-looking type alias is building our index sequence. We take the tuple's size (std::tuple_size<std::tuple<Ts...>>::value) and pass it to std::make_index_sequence, which gives us an std::index_sequence<0, 1, 2, ..., N-1>. Now we need to get that index sequence as a parameter pack. We can do that with one extra layer of indirection to get type inference.

template <typename Input, typename... Ts, std::size_t... Is>
auto apply(const std::tuple<Ts...>& tpl, Input x, std::index_sequence<Is...>) {
  auto op = ApplyOp<Input, Ts...>();
  return op.apply(x, std::get<Is>(tpl)...);
}

template <typename Input, typename... Ts>
auto apply(const std::tuple<Ts...>& tpl, Input x) {
  using seq = std::make_index_sequence<std::tuple_size<std::tuple<Ts...>>::value>;
  return apply(tpl, x, seq());
}

The second apply is the one outside users call. They pass a tuple and an input value. Then we construct an std::index_sequence of the appropriate type and pass that to the first apply, which uses that index sequence to access each element of the tuple in turn.

Complete, runnable example

万水千山粽是情ミ 2025-02-20 00:37:12

主要区别是我需要每个调用的结果
下一个的输入。

将fold-expryse应用于分配操作员

template <typename... Args, typename Input>
auto my_apply(std::tuple<Args...> const &tpl, Input x) {
  return std::apply([&x](auto... op) {
    return ((x = op.apply(x)), ...);
  }, tpl);
}

demo

您可以引入一个反向顺序的假人变量

template <typename... Args, typename Input>
auto my_apply(std::tuple<Args...> const &tpl, Input x) {
  return std::apply([&x](auto... op) {
    int dummy;
    (dummy = ... = ((x = op.apply(x)), 0));
    return x;
  }, tpl);
}

https://godbolt.org/z/9wvshrke4“ rel =” nofollow noreferrer“> demo

The main difference is that I need each invocation's result as the
input for the next one.

Apply fold-expression to assignment operator

template <typename... Args, typename Input>
auto my_apply(std::tuple<Args...> const &tpl, Input x) {
  return std::apply([&x](auto... op) {
    return ((x = op.apply(x)), ...);
  }, tpl);
}

Demo

You can introduce an dummy variable for reverse order

template <typename... Args, typename Input>
auto my_apply(std::tuple<Args...> const &tpl, Input x) {
  return std::apply([&x](auto... op) {
    int dummy;
    (dummy = ... = ((x = op.apply(x)), 0));
    return x;
  }, tpl);
}

Demo

没有递归的一种方式是使用折叠表达式。

不幸的是,没有呼叫组成操作员折叠。

但是您可以创建自定义类型和转移常规操作员:

template <typename T>
struct Wrapper
{
    T t;
};

// Deduction guide, not needed in C++20
template <typename T> Wrapper(T) -> Wrapper<T>;

// Then the operator with changed semantic
template <typename T1, typename T2>
auto operator+(const Wrapper<T1>& lhs, const Wrapper<T2>& rhs)
{
    return Wrapper{lhs.t.apply(rhs.t)};
}

template <typename T1, typename T2>
auto operator-(const Wrapper<T1>& lhs, const Wrapper<T2>& rhs)
{
    return Wrapper{rhs.t.apply(lhs.t)};
}

// And now, the function with fol expression

template <typename... Args, typename Input>
auto my_apply(std::tuple<Args...> const &tup, Input x) {
    return std::apply([&](auto&...args){
        return (Wrapper<const Args&>{args} + ... + Wrapper<Input&>{x});
    }, tup).t;
}

template <typename... Args, typename Input>
auto my_apply_rev(std::tuple<Args...> const &tup, Input x) {
    return std::apply([&](auto&...args){
        return (Wrapper<Input&>{x} - ... - Wrapper<const Args&>{args});
    }, tup).t;
}

使用类似于

// std::size(std::to_string(10 * 10));
my_apply(std::tuple{ LengthOp{}, ToStringOp{}, SquareOp{}}, 10);
my_apply_rev(std::tuple{ SquareOp{}, ToStringOp{}, LengthOp{}}, 10);

demo

One way without recursion is to use fold expression.

Unfortunately, there is no call composition operator folding.

But you might create custom type and divert regular operator:

template <typename T>
struct Wrapper
{
    T t;
};

// Deduction guide, not needed in C++20
template <typename T> Wrapper(T) -> Wrapper<T>;

// Then the operator with changed semantic
template <typename T1, typename T2>
auto operator+(const Wrapper<T1>& lhs, const Wrapper<T2>& rhs)
{
    return Wrapper{lhs.t.apply(rhs.t)};
}

template <typename T1, typename T2>
auto operator-(const Wrapper<T1>& lhs, const Wrapper<T2>& rhs)
{
    return Wrapper{rhs.t.apply(lhs.t)};
}

// And now, the function with fol expression

template <typename... Args, typename Input>
auto my_apply(std::tuple<Args...> const &tup, Input x) {
    return std::apply([&](auto&...args){
        return (Wrapper<const Args&>{args} + ... + Wrapper<Input&>{x});
    }, tup).t;
}

template <typename... Args, typename Input>
auto my_apply_rev(std::tuple<Args...> const &tup, Input x) {
    return std::apply([&](auto&...args){
        return (Wrapper<Input&>{x} - ... - Wrapper<const Args&>{args});
    }, tup).t;
}

Usage similar to

// std::size(std::to_string(10 * 10));
my_apply(std::tuple{ LengthOp{}, ToStringOp{}, SquareOp{}}, 10);
my_apply_rev(std::tuple{ SquareOp{}, ToStringOp{}, LengthOp{}}, 10);

Demo

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文