3

I have the following loop in my code:

for (const auto& item : vec) {...}

In order to find the position of item in vec, I do:

size_t pos = ((size_t)&item-(size_t)vec.data())/sizeof(item);

But it feels kinda "C" (in opposed to "C++").

I know that I can achieve this purpose if I change my loop-format to either one of the following:

  • for (size_t i = 0; i < vec.size(); i++) {...}
  • for (auto it = vec.begin(); it != vec.end(); it++) {...}

But I would like to refrain from doing this.

Bottom line questions:

  1. Is my method guaranteed by the language standard?
  2. Is there a more "C++" way to do this without changing the loop-format?

Thank you.

goodvibration
  • 5,980
  • 4
  • 28
  • 61
  • 1
    `std::find`/`std::find_if` or `for (auto it = vec.begin(); ...` mentioned by you are the C++ ways of doing this. Range-based for loop is the last thing you'd want to use. – LogicStuff May 11 '17 at 08:25
  • 2
    Why do you care about the position? If it's necessary information, then there's nothing wrong with indexing the vector using a traditional loop. – George May 11 '17 at 08:28
  • @LogicStuff: Thank you, but `find` forces me to implement an `operator==` for the type of my vector elements, plus I need the actual position I'm at, not the position of the first element which is identical to the element I'm currently working on (and to my understanding, that what `find` does). – goodvibration May 11 '17 at 08:29
  • 1
    personally i would just increment `pos` in the loop instead of `((size_t)&item-(size_t)vec.data())/sizeof(item)` – AndersK May 11 '17 at 08:29
  • 4
    `&item - &vec[0]` works. http://ideone.com/768yVQ – mch May 11 '17 at 08:33
  • @mch: Great!!! Is this guaranteed by the standard? – goodvibration May 11 '17 at 08:36
  • 1
    @goodvibration yup. It's guaranteed for both `std::vector` and `std::array`. Both are contiguous sequences.It will *not* work for `std::deque`, `std::list`, etc. – WhozCraig May 11 '17 at 08:37
  • 1
    Why all those downvotes? – Jabberwocky May 11 '17 at 08:38
  • @goodvibration I think it is, because vector storage is guaranteed to be contiguous. Also you could instead do `&item - vec.data()`, preferred. Finally: yes this method works, but seemingly innocent changes will break this in surprising ways. If you change your for loop to `auto` because you want a copy, it will completely break this technique. I can't really recommend this. – Nir Friedman May 11 '17 at 08:38
  • @WhozCraig: I think that it by the least needs a casting, i.e., `(size_t)&item-(size_t)&vec[0]`. I get a compilation error otherwise. – goodvibration May 11 '17 at 08:39
  • @goodvibration no it doesn't. It's pointer differencing in a contiguous sequence, and it's covered by the standard. Look at the link mch posted. And heed Nir's warning about references vs. copies. The code will still compile without issue if you drop the reference, and will certainly *not* be what you wanted. – WhozCraig May 11 '17 at 08:40
  • @WhozCraig: Ooops, you're right (had a different error). So no need to divide by `sizeof item`??? – goodvibration May 11 '17 at 08:41
  • 1
    @goodvibration no need for sizeof division. pointer-differencing does the math for you. And under no circumstances should you use a Base-class reference in your loop for a derived-type vector and a static-cast-to-Base on the latter's zero-element. As has been said numerous times, this is possible, but I certainly wouldn't do it. Id use an iterator construct. – WhozCraig May 11 '17 at 08:50
  • WhozCraig: "[...] warning about references vs. copies. [...] certainly *not* be what you wanted." Actually, it is undefined behaviour as subtracting pointers is only allowed if both operands point to the same array or one past this array... – Aconcagua May 11 '17 at 08:53
  • you can do that casting if you cast to intptr_t instead of size_t – The Techel May 11 '17 at 09:03

3 Answers3

4

But I would like to refrain from doing this.

Looping over an index/iterator is the exact thing you need here. Why would you refrain from doing that?


Is my method guaranteed by the language standard?

Just the fact that you have to ask this question to makes your method undesirable compared to a index/iterator loop. Regardless, I cannot see any reason why your method should fail.


Is there a more "C++" way to do this without changing the loop-format?

The most idiomatic C++ way is looping over an index/iterator. If you want a fancier version of that, you can wrap the logic in an higher-order function that provides the item as well:

template <typename Container, typename F>
void for_each_with_index(Container&& c, F&& f)
{
    for(std::size_t i = 0; i < c.size(); ++i)
    {
         f(c[i], i);
    }
}

Usage:

std::vector<item> v{/* ... */};
for_each_with_index(v, [](auto& item, std::size_t index)
{ 
    /* ... */ 
});

live example on wandbox

Vittorio Romeo
  • 90,666
  • 33
  • 258
  • 416
  • Thank you very much for patiently, politely and professionally addressing each and every bullet in my question (not something to take for granted here)!!! – goodvibration May 11 '17 at 08:35
3

You don't need to cast your types as you did in your example:

size_t pos = ((size_t)&item-(size_t)vec.data())/sizeof(item);

You can do pointer arithmetics more simplier as follows:

#include <iostream>
#include <vector>

int main() {
    std::vector<int> test { 0, 1, 2, 3, 4, 5, 6 };
    for(const auto& elem : test) {
        const auto pos = &elem - test.data();
        std::cout << "Element at position " << pos << " is: " << elem << std::endl;
    }
    return 0;
}

The reason it works because the type of &elem is const int* and the returning type of test.data() is int* so the pointer arithmetics works fine on them.


Note: if you forget the reference operator (&) in you range-based for loop the example above won't work:

for(const auto elem : test) { /* ... */ } // Warning: elem is a copy!
Akira
  • 4,385
  • 3
  • 24
  • 46
1

If you are really dying to do this in C++ without changing the loop format substantially, considering incorporating cpp itertools: https://github.com/ryanhaining/cppitertools/blob/master/README.md. It's your only hope!

vector<int> vec{2, 4, 6, 8};
for (auto&& e : enumerate(vec)) {
    cout << e.index
         << ": "
         << e.element
         << '\n';
}

It provides lots more tools, drawing inspiration from python which is known for having very nice ways of iterating. You can also iterate over two same size vectors together, de-nest nested for loops, etc.

Strictly speaking, you can also implement enumerate yourself. It's not that difficult but it is quite a few lines of boilerplate.

Nir Friedman
  • 17,108
  • 2
  • 44
  • 72