首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >“压缩”元组(for_each_in_tuples)上的迭代

“压缩”元组(for_each_in_tuples)上的迭代
EN

Code Review用户
提问于 2018-08-06 20:04:54
回答 3查看 1.4K关注 0票数 4

我希望实现一个函子和同一个size的一个或多个元组,并将这个函子应用到每个元组的D1i'th元素中。

示例:

代码语言:javascript
复制
#include 
#include 

std::tuple t1(1, 2.2, false);
std::tuple t2(3.3, 'a', 888);

std::cout << std::boolalpha;
for_each_in_tuples(
    [](auto a1, auto a2)
    {
        std::cout << a1 << ' ' << a2 << '\n';
    }, t1, t2);

// Outputs:
// 1 3.3
// 2.2 a
// false 888

执行情况:

代码语言:javascript
复制
#include 
#include 
#include 

namespace impl
{
template
struct First { using Type = T; };

template
using First_t = typename First::Type;

template
inline constexpr auto all_same = (... && (value == values));

template
inline constexpr auto tuple_size = std::tuple_size_v>;

template
constexpr void for_each_in_tuples(Function func, Tuples&&... tuples)
{
    constexpr auto size = tuple_size>;

    func(std::get(std::forward(tuples))...);
    if constexpr (index + 1 < size)
        for_each_in_tuples(func, std::forward(tuples)...);
}
}

template
constexpr void for_each_in_tuples(Function func, Tuples&&... tuples)
{
    static_assert(sizeof...(Tuples) > 0);
    static_assert(impl::all_same...>);

    impl::for_each_in_tuples(func, std::forward(tuples)...);
}

在编译器资源管理器:https://godbolt.org/g/cYknQT

主要问题:

  1. 这个实现是正确的吗?可以简化吗?
  2. 哪个名字更好,for_each_in_tuple还是for_each_in_tuples (或者……)?
EN

回答 3

Code Review用户

回答已采纳

发布于 2018-08-07 08:10:10

代码语言:javascript
复制
template
struct First { using Type = T; };

template
using First_t = typename First::Type;

这可以结合使用std::tuple_elementstd::tuple来完成。

代码语言:javascript
复制
template 
using First_t = std::tuple_element_t<0, std::tuple>;
代码语言:javascript
复制
template
inline constexpr auto all_same = (... && (value == values));

对于一个空包,您真的希望这个值是未定义的吗?空包的默认行为是&&true||被认为是false

代码语言:javascript
复制
static_assert(sizeof...(Tuples) > 0);

而不是失败,也许试着用0参数调用函数,看看会发生什么?

代码语言:javascript
复制
static_assert(impl::all_same...>);

如果您想要类似于压缩的行为,您将希望压缩元组,直到其中一个元组耗尽了它的元素(min大小而不是第一个大小)。

这个实现是正确的吗?可以简化吗?

是。使用迭代方法,如顺序展开,超过递归。

哪个名字更好,for_each_in_tuple还是for_each_in_tuples (或者……)?

也许是for_each_zipped。元组可以从函数的签名中收集。

如果要避免递归,请使用折叠表达式、std::index_sequencestd::make_index_sequence

从一个简单的助手开始,只调用函数,在特定索引处跨所有元组调用元素。

代码语言:javascript
复制
template 
constexpr void invoke_at(Function&& func, Tuples&&... tuples) {
    func(std::get(std::forward(tuples))...);
}

现在我们需要一种顺序调用它的方法(invoke_at<0>(args), invoke_at<1>(args), ..., invoke(args))。使用折叠表达式,就像对all_same一样,但使用逗号运算符和一元右折叠((invoke_at(args), ...))。为了生成扩展的Ns,我们使用std::index_sequence

代码语言:javascript
复制
template 
constexpr void apply_sequence(Function&& func,  std::index_sequence, Tuples&&... tuples) {
    (((void)invoke_at(std::forward(func), std::forward(tuples)...), ...));
}

最后,编写函数,检查先决条件,创建索引序列,并将参数转发给上面的助手。

代码语言:javascript
复制
template 
constexpr void tuple_for_each(Function&& func, Tuples&&... tuples) {
    static_assert(sizeof...(tuples) > 0, "Must be called with at least one tuple argument");

    constexpr auto min_length = std::min({std::tuple_size_v>...});
    if constexpr (min_length != 0) {
        impl::apply_sequence(std::forward(func),
                             std::make_index_sequence{},
                             std::forward(tuples)...);
    }
    else {
        func();
    }
}

注意-该扩展有一个强制转换为void,它禁用任何滥用逗号运算符的重载的恶作剧。

票数 4
EN

Code Review用户

发布于 2018-08-06 20:40:40

关于您的代码,我没什么可批评的;据我所见,每件事似乎都经过深思熟虑,而且写得很好。

关于问题1:是的,我认为你的实施是正确的。我也不认为有什么需要简化的;实现非常简洁,在得到constexpr whileconstexpr for (如果有的话)之前,消除递归是不可能的。

至于问题2,我非常赞成for_each_in_tuples,因为您显然是一次迭代多个元组。

如果有什么需要改进的地方,我会说是First。因为参数包是“类似”的类型列表,所以在我看来,Head这个名字更适合。此外,与正常函数参数一样,我认为保留未使用的参数不命名也是很好的方式。

代码语言:javascript
复制
template 
struct Head { using Type = T; };

不过,这确实只是个人偏好。

但是,您的代码至少有一个真正的“问题”,这与for_each_in_tuples中的D7参数有关:由于您通过值传递func,如果func不是一个轻量级函数对象(例如,lambda捕获了一些具有昂贵的复制操作的对象),则可能会导致性能下降。此外,这种按值传递的语义还会使用户无法使用根本不支持复制操作的函数对象,例如,lambda捕获任何仅移动类型的内容。为了解决这个问题,我建议您只在每个递归调用中使用std::move func,这将涵盖几乎所有的情况(除了昂贵的移动类型之外,但它们非常罕见)。

票数 3
EN

Code Review用户

发布于 2018-08-07 09:35:34

您的实现是正确的,即使Snowhawk很好地指出了可以改进的地方(尽管我认为递归方法并不比“顺序扩展”更复杂)。

我主要关注的是更一般的设计: 1) zip、tuple和2)在压缩元素上应用函数做了一件事太多了。它应该由两个函数组成:一个将函数应用到一个元组的成员(比如tuple_for_each)和一个压缩元组(比如tuples_zip)。这意味着您也可以在不需要压缩的情况下使用tuple_for_each,在不需要向压缩元素应用函数时也可以使用元组。

此外,for_each也失去了通用性,因为当您需要恢复正在应用的函数的结果时(可以使用lambda捕获,但这是一个扭曲),因此不能使用它。因此,我主张使用一个tuple_map函数,返回包含应用程序结果的元组(在有应用程序的情况下)。

tuple_map应该是这样的:

代码语言:javascript
复制
#include 
#include 

template 
constexpr auto tuple_map_impl(Fn&& fn, std::index_sequence, Tuple&& tuple) {
    // if fn's return type is void, do not return a tuple since it'd be invalid
    using fn_return_type = decltype(fn(std::get<0>(tuple)));
    if constexpr (std::is_same_v)
        (std::forward(fn)(std::get(std::forward(tuple))),...);
    else  
        return std::forward_as_tuple(std::forward(fn)(std::get(std::forward(tuple)))...);
    }

template 
constexpr auto tuple_map(Fn&& fn, Tuple&& tuple) {
    constexpr auto tsz = std::tuple_size_v>;
    // we need to handle the case where tuple size == 0 because we check fn(std::get<0>(tuple)) in tuple_map_impl
    if constexpr (tsz == 0) return std::forward(tuple);
    else return tuple_map_impl(std::forward(fn),
                               std::make_index_sequence>>(),
                               std::forward(tuple));
    }

tuple_zip是这样的:

代码语言:javascript
复制
template 
auto zip_tuples_at(Tuples&&... tuples) {
    return std::forward_as_tuple(std::get(std::forward(tuples))...);
    }

template 
constexpr auto indexes() {
    return std::make_index_sequence>>();
    }

template 
constexpr auto tuples_zip_impl(std::index_sequence, Tuples&&... tuples) {
    return std::make_tuple(zip_tuples_at(tuples...)...);
    }

template 
constexpr auto tuples_zip(Tuples&&... tuples) {
    return tuples_zip_impl(indexes(), std::forward(tuples)...);
    }

然后,我们可以用这两个函数和zipped_tuples_map组成std::apply(我对函数名称的建议):

代码语言:javascript
复制
template 
auto zipped_tuples_map(Fn&& fn, Tuples&&... tuples) {
    return tuple_map([&fn](auto&& zipped_elements) { return std::apply(std::forward(fn), zipped_elements); },
                     tuples_zip(std::forward(tuples)...));
    } 

指向完整示例的链接:魔杖盒

票数 2
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/201106

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档