0

I'm looking for a convenient option to get the last element from a container/range which only supports forward traversal, which essentially means I cannot do stuff like std::prev(std::end()). For now the only idea I have is this one:

const std::forward_list<int> list{1, 2, 3, 4, 5};
auto it = list.cbegin();
std::advance(it, std::distance(it, list.cend()) - 1);
std::cout << *it << std::endl;

Is there a more convenient way to get to the last element of a forward traversal range? Boost is welcome.

16
  • 3
    @Irelia Forward iterators cannot move backwards, your code won't compile. Commented Apr 25, 2023 at 22:06
  • 4
    Walk the list with a forward sniffing iterator, return the previous successful iterator. Commented Apr 25, 2023 at 22:08
  • 4
    I'd say you have XY problem. Why did you choose a container with forward iterator only if you need to access last element? It is possible, as Eljay said, but it's probably not a good idea. Commented Apr 25, 2023 at 22:14
  • 3
    @Kaiyakha well, teamwork means to discuss such design decision amongst the whole team with equal rights to address concerns, no? Commented Apr 25, 2023 at 22:17
  • 1
    Might be worth working up a business case demonstrating that use of algorithm X allows delivery of that module in Time t, but increases the delivery time of the four modules that use it to tn, ultimately delaying the project more than tn for module X and only t for the other four. Commented Apr 25, 2023 at 22:18

2 Answers 2

4

make a helper function that iterates until the next one would the end

template<class iterator>
iterator find_next_to_last(iterator first, iterator last) {
    assert(first != last);
    iterator next = first;
    while(++next != last)
        first = next;
    return first;
}
Sign up to request clarification or add additional context in comments.

2 Comments

Should also check if (first == last) for an empty list, unless empty list is disallowed by contract (in which case assert(first != last);).
@Eljay good catch! After some thought, I decided to disallow by contract.
1

I like Mooing Duck's answer, but I thought a tiny tweak to handle an empty list would be nice. Although nothing wrong with disallowing that situation by contract, plenty of functions in the standard library impose constraints by contract.

But to answer your question...

Is there a more convenient way to get to the last element of a forward traversal range?

...no, there is not a more convenient way in general. You need to walk the forward list to get to the back most element. (Assuming you can't cache an iterator to the back most element.)

#include <forward_list>
#include <iostream>
#include <iterator>
#include <utility>
#include<cassert>

template <typename I>
auto back(I it, I last) {
    using std::next;
    auto n = it;

    while (n != last) {
        it = std::exchange(n, next(n));
    }

    return it;
}

// Container-to-iterator-pair convenience function.
template <typename C>
auto back(C const& c) {
    using std::begin;
    using std::end;
    return back(begin(c), end(c));
}

static
auto operator<<(std::ostream& out, std::forward_list<int> const& l) -> std::ostream& {
    auto sep = "";

    for (auto&& x : l) {
        out << std::exchange(sep, " ") << x;
    }

    return out;
}

template <typename C, typename I>
struct print {
    C const& container;
    I const& it;
    print(C const& c, I const& i) : container{c}, it{i} {}
    bool valid() const {
        using std::begin;
        using std::end;
        using std::next;
        auto b = begin(container);
        auto e = end(container);
        while(b != e) {
            if (it == b) return true;
            b = next(b);
        }
        return it == e;
    }

    template <typename CC, typename II>
    friend auto operator<<(std::ostream& out, print<CC, II> const& p) -> std::ostream& {
        using std::end;
        assert(p.valid());

        if (end(p.container) == p.it) {
            return out << "(end)";
        }

        return out << *p.it;
    }
};
print(struct allow_ctad_t, struct allow2_ctad_t)->print<void, void>;

int main() {
    std::forward_list<int> const list{1, 2, 3, 4, 5};
    std::cout << list << "\n";
    auto it = back(list);
    std::cout << print(list, it) << "\n";
    std::cout << print(list, list.end()) << "\n";
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.