-1

Is there an iterator, perhaps in the standard library, akin to back_inserter in the sense of calling a function on being dereferenced, that when dereferenced, returns not the item it's pointing to but an attribute of it?

I want to take an iterator to the start and end of a container (e.g. a vector) of custom-defined structs and I want the iterator on being dereferenced to access a specific member of that struct and to work on that directly. Perhaps I can just define that behaviour by having it invoke a lambda of some sort on that element that can then return the desired member field.

Reason being is I don't want to maintain a separate container of those member fields.

Something like this:

struct Person
{
    std::string m_Name;
    std::uint64_t m_Age;
};

int main()
{
    std::vector<Person> people { Person("a", 15), Person("b", 25), Person("c", 40) };
    // MagicIterator is what I'm after
    auto ageIter = MagicIterator(people, [](const Person &person) { return person.m_Age; });
    // sample use case, foo here expects a container of arithmetic types
    auto out = foo<std::uint64_t>(ageIter.begin(), ageIter.end());
    return 0;
}

Right now, I think I'd first have to extract out all the ages in a separate container and then pass that new container to foo which I want to avoid.

I'm happy with anything up to and including C++17, Boost is okay too.

Nobilis
  • 7,310
  • 1
  • 33
  • 67
  • Not that I know of, but you could make your own using that lambda approach. – Hatted Rooster Jun 22 '21 at 11:32
  • 3
    Look at [boost transform_iterator](https://www.boost.org/doc/libs/1_76_0/libs/iterator/doc/html/iterator/specialized/transform.html) (or projection in ranges). – Jarod42 Jun 22 '21 at 11:33
  • @Jarod42 ah, thanks that might be just the ticket – Nobilis Jun 22 '21 at 11:37
  • @HattedRooster I thought about it but I was just curious if something like what I need really doesn't exist, does feel like a common use case. Looks like Boost might be the answer. – Nobilis Jun 22 '21 at 11:44
  • why dont you use some `void foo(Iter begin, Iter end)` instead of accumulate when accumulate does not have the problem you need an answer for? – 463035818_is_not_an_ai Jun 22 '21 at 12:37
  • @463035818_is_not_a_number fair enough, I didn't realise it would confusion, i've amended it. – Nobilis Jun 22 '21 at 12:42
  • 1
    I said it before, but I think I already said too much, so just for the record: Changing a question substantially after you received answers is not nice. Now there are answers that have nothing to do with the question at all. In case your question was misunderstood at first and you actually wanted to ask a different question it is better to open a new question – 463035818_is_not_an_ai Jun 22 '21 at 12:46

3 Answers3

1

You could just use std::accumulate().

    auto sumAges = std::accumulate(people.begin(), people.end(), 0,
                                   [](const Person &person){ return person.m_Age; });
Stephen M. Webb
  • 1,705
  • 11
  • 18
  • Ah, that's fair! But what if I didn't have that option? `accumulate` here was probably a bad example since it takes a lambda, whereas the actual function I want to use just takes two iterators. – Nobilis Jun 22 '21 at 11:35
  • @Nobilis what is the actual function you want to use? If you have a different question you should open a new question – 463035818_is_not_an_ai Jun 22 '21 at 12:19
0

Honestly, I wonder if you’re not complicating your life unnecessarily.

Why don’t you just use the following code ?

struct Person
{
    std::string m_Name;
    std::uint64_t m_Age;
};

int main()
{
    std::vector<Person> people{ { "a", 15 }, { "b", 25 }, {"c", 40 } };
    std::uint64_t sumAge = 0;
    for(auto it=people.begin(); it!=people.end(); it++){
        sumAge += it->m_Age;
    }
    return 0;
}
  • The example I gave was just an illustration of the kind of problem I have. I need to call something that takes a start/end iterator of doubles. I can extract those values from my container, store them in a different container and pass that to it but then I need to be dealing with two containers at a time. – Nobilis Jun 22 '21 at 11:37
0

Following on from a suggestion in the comments (and using this answer as a basis), I've come up with the following which does what I need (using std::accumulate just so I can pass it something that can operate on my example directly):

#include <iostream>
#include <vector>
#include <numeric>

#include <boost/iterator/transform_iterator.hpp>

struct Person
{
    std::string m_Name;
    std::uint64_t m_Age;

    Person(std::string name, std::uint64_t age) : m_Name{std::move(name)}, m_Age{age} {}
};


int main()
{
    std::vector<Person> people { Person("a", 10), Person("b", 15) };
    // lambda that accesses the member field I need to use
    auto ageGetter = [](const Person &person) { return person.m_Age; };
    // begin and end iterators that underneath call my 'ageGetter'
    auto begin = boost::make_transform_iterator(people.begin(), ageGetter);
    auto end = boost::make_transform_iterator(people.end(), ageGetter);
    // when run it prints 25, which is what I'd expect
    std::cout << std::accumulate(begin, end, 0) << std::endl;
    return 0;
}
Nobilis
  • 7,310
  • 1
  • 33
  • 67