1

This is more of a code clenliness question, cause I already have an example here. I'm doing this a ton in code and the creation of all these lambdas (some of which are the same) has begun to irk me.

So given the struct:

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;
};

I have a container of pointers to them, let's say vector<foo*> foos, now I want to go through the container and get the sum of one of the fields.
As an example if I wanted the field _r, then my current approach is to do this:

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

I'm writing this line everywhere. Can any improvement be made upon this? I'd really like to write something like this:

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

I don't think the standard provides anything like that. I could obviously write it, but then it would require the reader to go figure out my suspect code instead of just knowing what accumulate does.

Jonathan Mee
  • 37,899
  • 23
  • 129
  • 288
  • I don't see a problem with providing a small utility function. Sometimes people have to go lookup information about stuff in the standard library too. Few know it all by heart. So long as it's written cleanly and is accessible, why not add an abstraction over the standard library? – StoryTeller - Unslander Monica Jul 26 '17 at 14:09
  • @StoryTeller Right now I'm leaning toward that too. This question is my last stop before I go do it, just feels like there should be a better way. – Jonathan Mee Jul 26 '17 at 14:22
  • 1
    Have you considered a class method: "static void foo::acc4 (std::vector foos)" to perform all 4 accumulations in one pass? – 2785528 Jul 26 '17 at 14:26
  • @DOUGLASO.MOEN I had not... it's super viable for `struct foo` but mine actually has a _ton_ more members so that would be wasteful in my case. Tremendous suggestion though, if you feel like typing it out I'd definitely give you an upvote, though probably not an accept since it doesn't solve my problem. – Jonathan Mee Jul 26 '17 at 14:30
  • 1
    @StoryTeller Just found something pretty incredible in C++14. I think this is better than a function: https://stackoverflow.com/a/45443542/2642059 Thoughts? – Jonathan Mee Aug 01 '17 at 16:52

2 Answers2

1

Instead of writing a custom accumulate, I suggest writing a custom functor generator, that returns a functor that can be used as an argument to std::accumulate.

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

then

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

A few variations:

For containers of objects:

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

Use data member pointers instead of functions:

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

Support functors, rather than member function pointers:

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

Some (all?) of these variations could perhaps be combined to be selected automatically with some SFINAE magic, but that will increase complexity.

eerorika
  • 232,697
  • 12
  • 197
  • 326
1

There is actually a really elegant way to solve this using Variable Templates which were introduced in . We can templatize a lambda variable using the method pointer as the template argument:

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

Passing the func appropriate specialization of func as the last argument to accumulate will have the same effect as writing out the lambda in place:

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

Live Example


Another alternative based off the same templatization premise, which does not require , is the templatized function suggested by StoryTeller:

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

Which could also be used by simply passing the method pointer:

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

Live Example


The specificity required by both these examples has been removed in where we can use auto for template parameter types: http://en.cppreference.com/w/cpp/language/auto This will allow us to declare func so it can be used by any class, not just foo:

template <auto T>
auto func(const auto init, const auto i) { return init + (i->*T)(); }
Jonathan Mee
  • 37,899
  • 23
  • 129
  • 288
  • @StoryTeller The concept seems to make sense. I even started updating my answer, but for some reason I'm having trouble passing the templatized function's address to `accumulate`: http://ideone.com/WTfxQR I'm pretty sure this is legit: https://stackoverflow.com/q/38402133/2642059 What am I doing wrong here? – Jonathan Mee Aug 01 '17 at 17:30
  • 1
    [You missed the const qualifier on the pointer to member](http://ideone.com/FhrIlc). I deleted my post because the C++98 solution isn't quite as robust as the generic lambda (as your example demonstrates by naming the types). – StoryTeller - Unslander Monica Aug 01 '17 at 17:44
  • @StoryTeller Thanks, I knew I'd missed something. C++17 lets us go even a bit further, preventing me from making such mistakes: `template auto func(const auto init, const auto i) { return init + (i->*T)(); }` – Jonathan Mee Aug 01 '17 at 17:49
  • Yup, the language is becoming amazing at the amount of expressive-ness it allows. The cost is an extremely complicated standard however. – StoryTeller - Unslander Monica Aug 01 '17 at 17:52
  • 1
    I feel I can also share that I've used the template function way when working with legacy C APIs. The library required *a lot* of callbacks. I eventually divined a simple template that allowed me to generate those forwarding to member callbacks easily. Was very proud of myself :) As an added bonus, It made tracing the callback calls really easy. All it took is one breakpoint in the template function. – StoryTeller - Unslander Monica Aug 01 '17 at 17:56