【问题标题】:Fold expressions in algorithms for std::tuple access [duplicate]std::tuple 访问算法中的折叠表达式
【发布时间】:2020-12-26 21:53:51
【问题描述】:

在处理包含其他实体的某种组合的通用结构时,我经常最终使用std::tuple,需要对std::tuple 的各个元素应用操作。例如,当我实现"zip iterator" 时,底层范围和迭代器存储在std::tuple 中。虽然使用std::index_sequence<std::tuple_size<Tuple>> 破解自定义函数或类模板以获取相关元素是相当直接的,但似乎使用在std::tuples 或std::tuple 类似结构上运行的算法(例如std::array 或@ 987654330@) 可以改进代码。

有些操作很容易实现。例如,tuple_for_each() 可以调度到递归实现或基于std::index_sequence 的实现以应用元素:

template <typename Tuple, typename Fun>
void tuple_for_each(Tuple&& tuple, Fun fun)
{
    auto const impl = [&tuple, fun]<std::size_t...I>(std::index_sequence<I...>){
        (fun(std::get<I>(tuple)), ...);
    };
    impl(std::make_index_sequence<std::tuple_size_v<std::decay_t<Tuple>>>());
}

当操作不是像for_eachtransform 中那样真正按元素进行操作时(因为后者产生返回类型更有趣),但将函数应用于元素的结果组合起来产生结果,通用算法似乎并不那么简单。该算法有点像accumulateinner_product。这些可以在自定义设置中很容易地编写,例如,

template <typename... T>
struct some_struct {
    std::tuple<T...> tuple;

    template <typename Tuple, std::size_t... I>
    static bool equals(Tuple&& t0, Tuple&& t1, std::index_sequence<I...>) {
        return ((std::get<I>(t0) == std::get<I>(t1)) && ...);
    }
    bool operator== (some_struct const& other) const {
        return equals(this->tuple, other.tuple, std::make_index_sequence<sizeof...(T)>());
    }
};

如果这个operator==() 可以委托给一个合适的tuple 算法就好了:

bool operator== (some_struct const& other) const  {
    return tuple_inner_product(this->tuple, other.tuple, true, std::equal_to<>(), std::logical_and<>());
}

除了根据最后一个参数的类型创建特殊版本之外,似乎没有办法使用折叠表达式实现tuple_inner_product。我知道如何实现递归版本,但如果组合操作是逻辑操作数之一,我认为该版本不会使评估短路(在调用函数之前总是需要确定函数参数):

template <typename T0, typename T1, typename Init, typename Transform, typename Combine>
auto tuple_inner_product(T0&& t0, T1&& t1, Init init, Transform transform, Combine combine) {
    auto const recurse = [&t0, &t1, transform, combine]<std::size_t I>(
                               std::integral_constant<std::size_t, I>,
                               auto const& r, auto init) {
        if constexpr (I == std::min(std::tuple_size_v<std::decay_t<T0>>,
                                    std::tuple_size_v<std::decay_t<T1>>)) {
            return init;
        }
        else {
            return combine(transform(std::get<I>(t0), std::get<I>(t1)),
                           r(std::integral_constant<std::size_t, I+1>(), r, init));
        }
    };
    return recurse(std::integral_constant<std::size_t, 0u>(), recurse, init);
}

因此,问题就变成了是否有办法使用折叠表达式来实现这个算法?

【问题讨论】:

  • 你见过 std::apply 吗?您的两个示例看起来都很容易用它编写。 (现在无法实际编写和测试它。)
  • 你基本上想要this
  • @HTNW:我认为std::apply() 有点相反:它调用一个函数扩展std::tuple 元素成为函数参数。我想对每个元素应用一个函数。例如,如果 std::tuple 包含不同序列的迭代器,例如 tuple_for_each(tuple, [](auto&amp; it){ ++it; }) 变为 ++std::get&lt;0&gt;(tuple), ++std::get&lt;1&gt;(tuple), ...
  • HTNW 评论跟进:std::apply([](auto&amp;... its){(++its, ...);}, tuple);.
  • @DietmarKühl 我们开始吧:void tuple_for_each(auto &amp;&amp;t, auto f) { apply([&amp;](auto&amp;&amp;... xs) { (f(forward&lt;decltype(xs)&gt;(xs)), ...); }, forward&lt;decltype(t)&gt;(t)); } auto tuple_transform(auto &amp;&amp;t, auto f) { return apply([&amp;](auto&amp;&amp;... xs) { return make_tuple(f(std::forward&lt;decltype(xs)&gt;(xs))...); }, std::forward&lt;decltype(t)&gt;(t)); } bool tuple_eq(auto &amp;&amp;l, auto &amp;&amp;r) { return apply([&amp;](auto&amp;&amp;... ls) { return apply([&amp;](auto&amp;&amp;... rs) { return ((std::forward&lt;decltype(ls)&gt;(ls) == std::forward&lt;decltype(rs)&gt;(rs)) &amp;&amp; ...); }, forward&lt;decltype(r)&gt;(r)); }, forward&lt;decltype(l)&gt;(l)); }

标签: c++ c++20


【解决方案1】:

我将从生成整数常量元组的代码开始。

template<TupleLike Tuple>
constexpr auto tuple_indexes(Tuple&&);

对于 nary tuple,std::tuple&lt;std::integral_constant&lt;std::size_t,0&gt;, std::integral_constant&lt;std::size_t,1&gt;, ..., std::integral_constant&lt;std::size_t,N-1&gt;

现在我们得到:

bool operator== (some_struct const& other) const  { 
  return std::apply([&](auto...Is){
    return (true &&... && (std::get<Is>(this->tuple)==std::get<Is>(other.tuple)));
  }, tuple_indexes(this->tuple));
}

我也觉得有用的词汇函数是

template<std::size_t N>
using index_t=std::integral_constant<std::size_t,N>;
template<std::size_t N>
constexpr index_t<N> index={};

template<std::size_t...Is>
auto indexer_for(std::index_sequence<Is...>){
  return [](auto&& f)->decltype(auto){ return f(index<Is>...); };
}
template<std::size_t N>
auto indexer_upto(){
  return indexer_for(std::make_index_sequence<N>{});
}

这可以通过多种方式使用。例如,

bool operator== (some_struct const& other) const  { 
  return indexer_upto<sizeof...Ts>()([&](auto...Is){
    return (true &&... && (std::get<Is>(this->tuple)==std::get<Is>(other.tuple)));
  });
}

比我上面的索引元组更高效、更通用。

【讨论】:

  • std::tuple 创建std::index_sequence 的函数是一个好主意(尽管我认为它需要稍微不同的语法)以避免很多其他方面的混乱 - 谢谢!
  • @diet 您需要一种将索引序列扩展为索引的方法; apply 是预先写好的,或者你可以自己动手。例如,我为此写indexer(index_sequence)(lambda)
  • 感谢您的更新:我也没有看到在这里使用 integral_constant 并转换为 constexpr 值。这很酷!这样,我的问题实际上得到了回答[与声称的重复项不同]。
  • @diet 添加了另一个版本。索引元组的编译时间很昂贵。
猜你喜欢
  • 2021-04-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-05-13
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多