【问题标题】:Need to Total Fields From a Container of structs需要从结构容器中汇总字段
【发布时间】:2017-07-26 14:02:37
【问题描述】:

这更像是一个代码简洁问题,因为我在这里已经有一个示例。我在代码中做了大量的工作,所有这些 lambdas(其中一些是相同的)的创建已经开始让我厌烦了。

所以给定结构:

struct foo {
    int b() const { return _b; }
    int a() const { return _a; }
    int r() const { return _r; }
    const int _b;
    const int _a;
    const int _r;
};

我有一个指向它们的指针容器,比如说vector<foo*> foos,现在我想通过容器并获取其中一个字段的总和。
例如,如果我想要字段_r,那么我目前的做法是这样做:

accumulate(cbegin(foos), cend(foos), 0, [](const auto init, const auto i) { return init + i->r(); } )

我到处都在写这行。可以对此进行任何改进吗?我真的很想写这样的东西:

x(cbegin(foos), cend(foos), mem_fn(&foo::r));

我不认为标准提供了类似的东西。我显然可以编写它,但它需要读者找出我的可疑代码,而不是仅仅知道 accumulate 做了什么。

【问题讨论】:

  • 我认为提供一个小实用功能没有问题。有时人们也必须在标准库中查找有关内容的信息。很少有人能背诵这一切。只要它写得干净且易于访问,为什么不在标准库上添加一个抽象呢?
  • @StoryTeller 现在我也倾向于这个。这个问题是我做之前的最后一站,感觉应该有更好的方法。
  • 您是否考虑过一个类方法:“static void foo::acc4 (std::vector foos)”一次执行所有 4 次累加?
  • @DOUGLASO.MOEN 我没有......这对struct foo 来说是超级可行的,但我的成员实际上有 ton 更多的成员,所以这对我来说是一种浪费。不过,非常棒的建议,如果你想把它写出来,我肯定会给你一个赞成票,尽管可能不接受,因为它不能解决我的问题。
  • @StoryTeller 刚刚在 C++14 中发现了一些非常不可思议的东西。我认为这比函数更好:stackoverflow.com/a/45443542/2642059Thoughts?

标签: c++ stl sum containers accumulate


【解决方案1】:

我建议不要编写自定义累积,而是编写自定义函子生成器,它返回一个函子,该函子可用作std::accumulate 的参数。

template<class Fun>
auto mem_accumulator(Fun member_function) {
    return [=](auto init, auto i) {
        return init + (i->*member_function)();
    };
}

然后

accumulate(cbegin(foos), cend(foos), 0, mem_accumulator(&foo::r));

一些变化:

对于对象容器:

template<class MemFun>
auto mem_accumulator(MemFun member_function) {
    return [=](auto init, auto i) {
        return init + (i.*member_function)();
    };
}

使用数据成员指针代替函数:

template<class T>
auto mem_accumulator(T member_ptr) {
    return [=](auto init, auto i) {
        return init + i->*member_ptr;
    };
}
// ...
accumulator(&foo::_r)

支持函子,而不是成员函数指针:

template<class Fun>
auto accumulator(Fun fun) {
    return [=](auto init, auto i) {
        return init + fun(i);
    };
}
// ...
accumulator(std::mem_fun(&foo::r))

这些变体中的一些(全部?)也许可以结合使用一些 SFINAE 魔法自动选择,但这会增加复杂性。

【讨论】:

    【解决方案2】:

    实际上有一种非常优雅的方法可以使用Variable Templates 来解决这个问题, 中介绍了该方法。我们可以使用方法指针作为模板参数来模板化一个 lambda 变量:

    template <int (foo::*T)()>
    auto func = [](const auto init, const auto i){ return init + (i->*T)(); };
    

    func 的适当特化func 作为accumulate 的最后一个参数传递将具有与在适当位置写出lambda 相同的效果:

    accumulate(cbegin(foos), cend(foos), 0, func<&foo::r>)
    

    Live Example


    另一个基于相同模板化前提的替代方案,不需要,是模板化函数suggested by StoryTeller

    template <int (foo::*T)()>
    int func(const int init, const foo* i) { return init + (i->*T)(); }
    

    也可以通过简单地传递方法指针来使用:

    accumulate(cbegin(foos), cend(foos), 0, &func<&foo::r>)
    

    Live Example


    这两个示例所需的特殊性已在 中删除,我们可以将 auto 用于模板参数类型:http://en.cppreference.com/w/cpp/language/auto 这将允许我们声明 func,以便 使用任何类,而不仅仅是foo

    template <auto T>
    auto func(const auto init, const auto i) { return init + (i->*T)(); }
    

    【讨论】:

    • @StoryTeller 这个概念似乎很有意义。我什至开始更新我的答案,但由于某种原因,我无法将模板化函数的地址传递给accumulateideone.com/WTfxQR 我很确定这是合法的:stackoverflow.com/q/38402133/2642059 我在这里做错了什么?跨度>
    • You missed the const qualifier on the pointer to member。我删除了我的帖子,因为 C++98 解决方案不如通用 lambda 强大(正如您的示例通过命名类型所展示的那样)。
    • @StoryTeller 谢谢,我知道我错过了一些东西。 C++17 让我们更进一步,防止我犯这样的错误:template &lt;auto T&gt; auto func(const auto init, const auto i) { return init + (i-&gt;*T)(); }
    • 是的,这种语言在其所允许的表现力方面变得令人惊叹。然而,成本是一个极其复杂的标准。
    • 我觉得我也可以分享一下我在处理遗留 C API 时使用了模板函数的方式。该库需要很多回调。我最终找到了一个简单的模板,让我可以轻松地生成转发给成员的回调。为自己感到非常自豪 :) 作为额外的奖励,它使跟踪回调调用变得非常容易。只需在模板函数中设置一个断点即可。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-09-14
    • 2016-07-26
    • 1970-01-01
    • 2014-08-04
    • 1970-01-01
    • 2021-08-20
    • 2012-04-01
    相关资源
    最近更新 更多