Sign in to follow this  
Lode

std::list: iterator or const_iterator?

Recommended Posts

Lode    1003
Hi, I had a datastructure (a contour existing out of points and edges) implemented as std::vector, and some struct that represents indices of two edges. These indices were of course just integers, the index of something in an std::vector. However I decided that it's better to use std::list instead of std::vectors. So the indices would have to become iterators of the std::list. But... I was using these indices both for const Contours and non-const Contours. An integer index of an std::vector can be used both for const and non const ones. The const-ness of the std::vector determines what you can do. If I'd switch to std::list, what do I have to use in my struct, const_iterator or iterator? const_iterator will make my struct useless when working on a non-const list and wanting to change something. Non-const iterators can't be gotten from a const list (AFAIK). So nothing will work! Is there any equivalent as integers to std::vector, but then for std::lists?

Share this post


Link to post
Share on other sites
cache_hit    614
Even with a vector you cant modify the contents through a const_iterator. The fact that its really just an int is irrelevant, the whole point of an iterator is to hide that from you. Im not sure i understand the problem though, youre saying that you cant get an iterator to a const list, but thats the whole point. If youre saying you *need* to be able to modify a const list maybe theres a design flaw somewhere

Share this post


Link to post
Share on other sites
Sneftel    1788
Quote:
However I decided that it's better to use std::list instead of std::vectors.
First of all, why? If it's because middle insertion/deletion is faster, there are potentially ways to use std::vector for that.

Quote:
If I'd switch to std::list, what do I have to use in my struct, const_iterator or iterator? const_iterator will make my struct useless when working on a non-const list and wanting to change something. Non-const iterators can't be gotten from a const list (AFAIK).


The following code converts a const iterator into a non-const iterator, given non-const access to its container.

template<typename ContainerType>
typename ContainerType::iterator unconstify(ContainerType & container, typename ContainerType::const_iterator iter)
{
std::iterator_traits<typename ContainerType::const_iterator>::difference_type index =
std::distance(const_cast<ContainerType const&>(container).begin(), iter);
typename ContainerType::iterator res = container.begin();
std::advance(res, index);
return res;
}

Oh, const correctness, you're totally worth O(n) conversions! [wink] (Incidentally, that code will work in O(1) on vectors.) You might consider doing that in debug mode, and this in release mode:

template<typename ContainerType>
typename ContainerType::iterator unconstify(ContainerType & container, typename ContainerType::const_iterator iter)
{
return reinterpret_cast<typename ContainerType::iterator &>(iter);
}

...which is, of course, utterly unsafe, but will probably work on your particular STL implementation. Maybe. (Anyone wanna weigh in on whether iterator checking and/or COW will screw it up?)

Share this post


Link to post
Share on other sites
Lode    1003
Quote:
Original post by cache_hit
Even with a vector you cant modify the contents through a const_iterator.


But I used a simple integer index there, instead of the iterator type :)

Quote:
Original post by Sneftel
First of all, why? If it's because middle insertion/deletion is faster, there are potentially ways to use std::vector for that.


What kind of ways?

I thought indeed to do it with std::lists for those cases of insertions in the middle and such, but other than that I'd pick std::vectors for the convenience factor.

So any argument in favor of std::vectors for representing polygon contours is very good news to me! :)

Share this post


Link to post
Share on other sites
Zipster    2365
If the std::vector was more convenient, then that's what I would use until I determined that middle insertions and deletions were a bottleneck. It seems like you're making things more difficult on yourself, although admittedly indices into a vector need to be update if it's modified so perhaps that's an issue?

Alternatively, try to eliminate the indices altogether. If you're using them to index adjacent edges, then adjacency in the vector itself could tell you the same thing.

Share this post


Link to post
Share on other sites
cache_hit    614
Quote:
Original post by Lode
Quote:
Original post by cache_hit
Even with a vector you cant modify the contents through a const_iterator.


But I used a simple integer index there, instead of the iterator type :)

Quote:
Original post by Sneftel
First of all, why? If it's because middle insertion/deletion is faster, there are potentially ways to use std::vector for that.


What kind of ways?

I thought indeed to do it with std::lists for those cases of insertions in the middle and such, but other than that I'd pick std::vectors for the convenience factor.

So any argument in favor of std::vectors for representing polygon contours is very good news to me! :)


It's possible I'm just missing something, but I still dont' see the issue. When you had indices and vectors you said you were just fine and you were able to modify the vector. If that's the case, then obviously didn't have a const vector.

Well, if you don't have a const list, you can perfectly well use list::iterator and modify items with no problem.

If you do have a const list, then use list::const_iterator. It seems like you feel like you have to make a global decision between *either* iterator *or* const_iterator. You can use both, if the list is const use const_iterator always. If the list is not const, use iterator if you're writing to the list, and const_iterator if you're reading from the list.


Even if you do end up deciding that vector is more appropriate than list for what you're doing, just make sure it's for the right reason - because I'm not sure that the issue you originally asked about with list is even an issue at all.

Share this post


Link to post
Share on other sites
Sneftel    1788
Quote:
Original post by cache_hit
It's possible I'm just missing something, but I still dont' see the issue.

The issue is that there is a need to refer to connectivity in a constness-independent form. A non-const iterator or pointer guarantees access with no further qualifications; a const iterator or pointer restricts access utterly, even to users that should be able to acquire it. An integer index does neither of these thing: It allows read-only access to the element given const access to the referenced container, and read-write access to the element given non-const access to the referenced container.

Share this post


Link to post
Share on other sites
cache_hit    614
Quote:
Original post by Sneftel
Quote:
Original post by cache_hit
It's possible I'm just missing something, but I still dont' see the issue.

The issue is that there is a need to refer to connectivity in a constness-independent form. A non-const iterator or pointer guarantees access with no further qualifications; a const iterator or pointer restricts access utterly, even to users that should be able to acquire it. An integer index does neither of these thing: It allows read-only access to the element given const access to the referenced container, and read-write access to the element given non-const access to the referenced container.


Sure, but certainly you *know* whether or not you have a const container right? If you have a const container, perhaps because you're in a const member function of the containing class, or because you have a const reference / pointer to containing class, then you declare an iterator of type const_iterator. If you aren't under that restriction, you make an iterator.

The only difference is that with an index you don't actually have to spell out whether you want read/write access or read-only access. But it's still explicit in the code, because you're either writing to the collection or you're not. If you're about to do something that writes to the list, declare an iterator of type list<T>::iterator. Otherwise declare an iterator of type list<T>::const_iterator.

I seem to be the only person in this thread that doesn't get it, could you post a short code sample illustrating the problem?

Share this post


Link to post
Share on other sites
cache_hit    614
After thinking about it some more, the only example I can think of where I can understand a possible issue is if you have some const function which is supposed to do some preliminary work, searching or whatever, and then cache the location of some value (i.e an iterator) that is of interest. Then later you go through and need to modify the value. If that's the case then I would argue that the search function shouldn't have been const in the first place. If it's an issue though, I would suggest considering whether or not the use of 'mutable' would be warranted

Upon re-reading the original post, it seems like the OP is trying to use the same data structure to operate on both const contours and contours. In this case, it's hard to say whether it's an issue or not unless I know what "const contour" means. Is this some template class that is sometimes parameterized as either SomeClass<contour>, and sometimes as SomeClass<const contour>? If so, the problem is as simple as using an is_const metafunction and typedefing the appropriate iterator as a private member of the class.

Maybe I'm still not getting it though. /shrug

Share this post


Link to post
Share on other sites
Sneftel    1788
It's difficult to give a straightforward code sample-- I just tried for about ten minutes and man was that a confusing example. I'll try to give a simple illustration of a related problem, and see if any lightbulbs go on.

Consider this binary tree node, and the evil things we can do with it.


class Node
{
public:
Node* GetParent() const;
Node* GetLeftChild() const;
Node* GetRightChild() const;
};

Node* evilly_subvert_constness(Node const* node)
{
return node->GetLeftChild()->GetParent();
}


Ew, right? We might be tempted to do this:

class Node
{
public:
Node* GetParent();
Node const* GetParent() const;
Node* GetLeftChild();
Node const* GetLeftChild() const;
Node* GetRightChild();
Node const* GetRightChild() const;
};


But that's not quite right either. Pointing const-ly to a Node says we're not gonna change it, but it doesn't -- and shouldn't -- say we can't change the things it knows about. It kinda feels right to restrict the interface in this way, but the fact that these are restrictions we're adding suggests that C++ doesn't think of constness as being quite this strong.

Also consider this:

Node const* find_closest_male_descendant_named_Frances(Node const* node);


Which is fine if we just want to inspect that relative. But what if we want to change it? We could make a non-const version of the code (requiring, BTW, the wholesale copy and paste and s/const//g of 18 auxiliary functions, representing 1300 lines of code, YIKES) but that doesn't feel right either, because all this searching in the family tree really SHOULD be guaranteed not to change anything.

What we really want is a way to talk about const-ness context for the family tree as a whole. Either we are a piece of code which is allowed to make babies and give them to people, or we are not. If we are, fine, all the code we need operates on const*s and returns const*s. If we are, however, all the constness in the world should not keep us from controlling Nodes that are under our control.

Share this post


Link to post
Share on other sites
cache_hit    614
Quote:
Original post by Sneftel
Also consider this:
*** Source Snippet Removed ***
Which is fine if we just want to inspect that relative. But what if we want to change it? We could make a non-const version of the code (requiring, BTW, the wholesale copy and paste and s/const//g of 18 auxiliary functions, representing 1300 lines of code, YIKES) but that doesn't feel right either, because all this searching in the family tree really SHOULD be guaranteed not to change anything.

What we really want is a way to talk about const-ness context for the family tree as a whole. Either we are a piece of code which is allowed to make babies and give them to people, or we are not. If we are, fine, all the code we need operates on const*s and returns const*s. If we are, however, all the constness in the world should not keep us from controlling Nodes that are under our control.


I've always preferred to think of const as meaning "not only will I not modify anything, I will not even do anything that makes it possible for someone else to modify it".

The problem with the Node example is that the term Node is being overloaded, because a Node really *is* a tree due to the recursive nature of the data structure. So, the second version of the Node data structure seems natural to me, although i admit it's frequently annoying to have to duplicate code in these types of functions.

I actually personally feel like const *should* be taken to mean that you're not allowed to change the state of things it knows about. I guess it depends on what you mean by "know about" though. If you want to make distinctions between statements like:

A : "I know about you, but I don't necessarily care about you as part of my state"
B : "I know about you, and you are an integral part of my behavior. A change to you is a change to me"

Then I think that's actually a legitimate use of the mutable keyword ('mutable' being used for situations of type A).


In your second example, I see again what you're saying but to me it's the same situation. C++'s definition of const means that not only will the function not change anything, it won't *expose* anything either. You shouldn't be able to get access to modify an object's state by calling operations you're allowed to perform if you're only allowed to perform read-only operations.


Take the OP's example (as much as we actually know about the example anyway) as a case in point. He said he's trying to make the same class operate with both Contours and ConstContours. Surely you should not be able to modify a ConstContour right? Otherwise what exactly *is* const about it?

When you really want to make the disctinction between A & B that I described above, I guess there's always the mutable keyword. Although I think I actually find a legitimate use for it less than once a year, I suppose it's possible this is one of those cases. I'd be interested to hear more about this Contour class though, and how he's using the same code to operate on const and non-const contours. I'm positive there's a solution involving some minor meta-programming, or a template specialization, or something that is type-safe as well as const correct, and still allows a list to be used (assuming that's even the best datastructure for the job).

Share this post


Link to post
Share on other sites
Sneftel    1788
Quote:
In your second example, I see again what you're saying but to me it's the same situation. C++'s definition of const means that not only will the function not change anything, it won't *expose* anything either. You shouldn't be able to get access to modify an object's state by calling operations you're allowed to perform if you're only allowed to perform read-only operations.
Const correctness is only as useful as long as it finds bugs. If you're not allowed to make your 1300-line find_closest_male_descendant_named_Frances module const-correct (even though it absolutely should not be allowed to change the passed-in Node), simply because you plan to use the result later in a non-const context, you're not getting the actual benefits of const.

Share this post


Link to post
Share on other sites
cache_hit    614
Quote:
Original post by Sneftel
Quote:
In your second example, I see again what you're saying but to me it's the same situation. C++'s definition of const means that not only will the function not change anything, it won't *expose* anything either. You shouldn't be able to get access to modify an object's state by calling operations you're allowed to perform if you're only allowed to perform read-only operations.
Const correctness is only as useful as long as it finds bugs. If you're not allowed to make your 1300-line find_closest_male_descendant_named_Frances module const-correct (even though it absolutely should not be allowed to change the passed-in Node), simply because you plan to use the result later in a non-const context, you're not getting the actual benefits of const.


I agree, except for that I think not having the function be const in the first place actually *is* const correct. The problem is just that people are used to visualizing "finds" as non-mutating operations, when really the function should be called "let_me_write_to_the_first_male_descendant_named_frances_if_such_a_descendant_exists".

In a good design, the whole find thing would ultimately just be a template function that returned an appropriate iterator type based on the argument type, so you wouldn't have the issue of having to maintain two versions of a 1300-line function and the compiler would even be smart enough to not generate additional code for the two versions.

But if a function's purpose is to expose write access to something, then by definition it shouldn't be const, and that is being const correct. The OP has a ConstContour, and to me that just screams out that he's doing something wrong if he's trying to find a way to modify it. Even if it's at a higher level, something is just wrong with that design.

Share this post


Link to post
Share on other sites
Sneftel    1788
Quote:
Original post by cache_hit
But if a function's purpose is to expose write access to something, then by definition it shouldn't be const, and that is being const correct.

That may be a self-consistent definition of const correctness, but it is not a very useful definition of const correctness. It forces you to forego the sole benefit of const correctness in large swaths of your code. And it does so needlessly, because we have these tools to wipe away constness by guaranteeing our permission to write to an object's collection.

Share this post


Link to post
Share on other sites
cache_hit    614
Quote:
Original post by Sneftel
Quote:
Original post by cache_hit
But if a function's purpose is to expose write access to something, then by definition it shouldn't be const, and that is being const correct.

That may be a self-consistent definition of const correctness, but it is not a very useful definition of const correctness. It forces you to forego the sole benefit of const correctness in large swaths of your code. And it does so needlessly, because we have these tools to wipe away constness by guaranteeing our permission to write to an object's collection.


Well I guess it's debatable whether or not it solves more problems than it causes. But it would be pretty scary if arbitrary code could gain write access to a data structure if they weren't explicitly given permission to write to it. Granted, they can already do that with const_cast, but at least with const_cast you can grep your codebase for it.

I think ultimately it just makes more sense to apply mutable in those cases though, as it requires no effort and is probably not even that bad a use of the keyword.

Share this post


Link to post
Share on other sites
Sneftel    1788
Quote:
Original post by cache_hit
Well I guess it's debatable whether or not it solves more problems than it causes. But it would be pretty scary if arbitrary code could gain write access to a data structure if they weren't explicitly given permission to write to it.
That's the thing -- you can make it so that they can only gain write access if they WERE given permission. You can do it in a type-safe and const-safe manner, without a single const_cast. (There's a const_cast in the templated function I posted earlier in the thread, but it's adding constness, not removing it.)

Here's another example. Suppose I'm a MouseManager. I distribute mouse information by means of MouseRegion objects. A MouseRegion gives information on mouse events that occur while the pointer is in a given region. Various components have pointers to different MouseRegions, but a lot of them only have const pointers, because they aren't allowed to change the MouseRegion in question. (The accessors they use to get the events, of course, are all const member functions.) Now suppose that a particular Window which holds a MouseRegion const* decides to split off a child window, and split its MouseRegion as well. Of course it can't do that through the const*. However, I (MouseManager) want to expose this capability even without const access, so I have a special method to do it without granting non-const access:
MouseRegion const* MouseManager::SplitRegion(MouseRegion const* region, int split_loc)

How am I to implement SplitRegion? Even though I control all the regions, and have non-const access to them, I'm not allowed to touch that one, because Window isn't allowed to touch that one.

Share this post


Link to post
Share on other sites
cache_hit    614
Thats a textbook (ignoring the fact that there actually *is* no real textbook for this sort of thing) use of const_cast. Const is really just trying to protect you from yourself. In this case, you have more information than the compiler. Its not unsafe at all to do the const_cast inside SplitRegion because presumably MouseManager is the only person that can create them.

Another solution would be to only expose const methods as part of MouseRegion's public interface and make MouseManager a friend of MouseRegion. Then you could return a non-const pointer to a MouseRegion

Share this post


Link to post
Share on other sites
iMalc    2466
An interesting discussion. I am certainly familiar with the issue of a search type of function in your class that doesn't itself modify the object but needs to be non-const because it returns an iterator which some callers may use to modify the colection.
It seems clear to me that the problem is the tying together of the thing that gives you information about the location of the things searched for, with the notion of whether that thing should be modifyable. As the original poster states, certainly for the case of a vector, an integer does not forcibly tie these two things together, whereas a [const_]iterator does.

Templating the function based on the returned iterator type seems to be one solution, but it certainly feels like a mediocre solution at best. Provided one of the callers instantiates the const version then you can be assured that the searching function isn't inadvertently modifying the object's state.

Share this post


Link to post
Share on other sites
Zipster    2365
I know I'm backtracking a bit, but I interpreted the problem to be something like this (probably because I've run into similar things before):

typedef std::list<Edge> Contour;
typedef std::vector<std::pair<Contour::iterator,Contour::iterator> > EdgePairs;


The dilemma being that if you have a const Contour, you can't add new elements to an EdgePairs from the contour because the former only accepts non-const iterators. On the other hand, if EdgePairs used const iterators, you wouldn't be able to modify any Edge in the Contour they belong to. In essence, by "fixing" the const-ness of the iterators, you're limiting what can be done when you have a Contour regardless of the contour's const-ness (a sort of const-coupling if you will). At the end of the day the only reason you're storing iterators is so that you can reference edges in the contour, but iterators force you to chose between const and non-cost versions even though the "true" const-ness of the reference changes depending on context; whether you have a const or non-cost Contour. Indexes into a vector are const-agnostic, which is why it worked perfectly before.

Share this post


Link to post
Share on other sites
Lode    1003
Quote:
Original post by cache_hit
If you do have a const list, then use list::const_iterator. It seems like you feel like you have to make a global decision between *either* iterator *or* const_iterator. You can use both, if the list is const use const_iterator always. If the list is not const, use iterator if you're writing to the list, and const_iterator if you're reading from the list.


Ah yes indeed, I have to make that global decision if I want to keep the code simple at least. I have a struct representing an intersection between two edges of the polygon, and that struct contains the x/y coordinate of the intersection, as well as the index of the two edges that are involved in the intersection. There may be situations where I want to use this for const contours and others where I want to use it for non const ones.

Of course I can simply define two different structs, one for the const and one for the non const case, but it's just a bit more complex and even adds a bit of code duplication. And then I can't use the same instance both in a const and a non const function.

E.g. having a function taking a const contour that gives as output all the intersections, then I can't use that output in another function that would *modify* the contour based on these found intersections, unless the "find intersections" function would take a non-const contour as parameter instead, which is a shame because that function only reads from it...

And the question was, I was wondering, if a simple integer can be an index for both const and non const std::vectors, was there also a simple way in C++ to have something be a iterator for both const and non const std::list, but the answer turns out to be no...

Share this post


Link to post
Share on other sites
mattd    1078
I'm probably missing something, but how about..:


#include <list>
#include <vector>

struct Point {
Point(int x, int y)
: x(x), y(y) {}

int x, y;
};

typedef std::list<Point> Points;

class Edge {
public:

typedef std::pair<Points::iterator, Points::iterator> PointsIterPair;

explicit Edge(const PointsIterPair& points)
: points(points) {}

Points::iterator FirstPt() { return points.first; }
Points::iterator SecondPt() { return points.second; }

Points::const_iterator FirstPt() const { return points.first; }
Points::const_iterator SecondPt() const { return points.second; }

private:

PointsIterPair points;
};

typedef std::vector<Edge> Edges;

struct Contour {
Points points;
Edges edges;
};

void NonConst(Contour& c) {
Points::iterator // ok
pt = c.edges.front().FirstPt();
}

void Const(const Contour& c) {
//Points::iterator // fails
Points::const_iterator // ok
pt = c.edges.front().FirstPt();
}

int main() {
Contour c;
c.points.push_back(Point(1, 1));
c.points.push_back(Point(2, 2));

Points::iterator it = c.points.begin();
c.edges.push_back(Edge(std::make_pair(c.points.begin(), ++it)));

NonConst(c);
Const(c);
}





You need non-const iterators to create an Edge pair, but if you have a const Contour object, you're not going to be able to modify its edges anyway, so it's a moot point(?)

Share this post


Link to post
Share on other sites
Sneftel    1788
Quote:
Original post by cache_hit
Thats a textbook (ignoring the fact that there actually *is* no real textbook for this sort of thing) use of const_cast. Const is really just trying to protect you from yourself. In this case, you have more information than the compiler. Its not unsafe at all to do the const_cast inside SplitRegion because presumably MouseManager is the only person that can create them.

Ah, but which MouseManager? A MouseManager should only SplitRegion()s that it itself created; otherwise any holder of a SplitRegion const* could split it, even without having write access to the MouseManager, by creating its own temporary MouseManager.

In any case, the point of this tangent was to try to describe a situation in which constness needed to be removed from a handle object. Since you're talking about const_cast on the pointer version, I assume that it worked?

Share this post


Link to post
Share on other sites
cache_hit    614
Quote:
Original post by Sneftel
Quote:
Original post by cache_hit
Thats a textbook (ignoring the fact that there actually *is* no real textbook for this sort of thing) use of const_cast. Const is really just trying to protect you from yourself. In this case, you have more information than the compiler. Its not unsafe at all to do the const_cast inside SplitRegion because presumably MouseManager is the only person that can create them.

Ah, but which MouseManager? A MouseManager should only SplitRegion()s that it itself created; otherwise any holder of a SplitRegion const* could split it, even without having write access to the MouseManager, by creating its own temporary MouseManager.

In any case, the point of this tangent was to try to describe a situation in which constness needed to be removed from a handle object. Since you're talking about const_cast on the pointer version, I assume that it worked?


Well, we agree that there are valid use cases for removing const. Also, it's true that there's the issue of needing a MouseManager to know which MouseRegions he created. I guess you could continue making the design more complicated, for example by storing a reference to the MouseManager in the MouseRegion. Either way though, yea there are times where const needs to be removed.

The const_iterator problem seems similar to the MouseManager problem in that the list is the only object that can realistically create the iterators. So given non-const access to the list, I see no reason why list<> member functions should not be allowed to unconstify its own iterators.

Of course, the original poster wants to know what IS possible, not what should be possible :P Now that he described his situation a bit more, I have more thoughts on his original question, but I'll respond to that separately since it's kind of a different thread.

[Edited by - cache_hit on February 18, 2010 8:39:40 AM]

Share this post


Link to post
Share on other sites
cache_hit    614
Quote:
Original post by Lode
Quote:
Original post by cache_hit
If you do have a const list, then use list::const_iterator. It seems like you feel like you have to make a global decision between *either* iterator *or* const_iterator. You can use both, if the list is const use const_iterator always. If the list is not const, use iterator if you're writing to the list, and const_iterator if you're reading from the list.


Ah yes indeed, I have to make that global decision if I want to keep the code simple at least. I have a struct representing an intersection between two edges of the polygon, and that struct contains the x/y coordinate of the intersection, as well as the index of the two edges that are involved in the intersection. There may be situations where I want to use this for const contours and others where I want to use it for non const ones.

Of course I can simply define two different structs, one for the const and one for the non const case, but it's just a bit more complex and even adds a bit of code duplication. And then I can't use the same instance both in a const and a non const function.

E.g. having a function taking a const contour that gives as output all the intersections, then I can't use that output in another function that would *modify* the contour based on these found intersections, unless the "find intersections" function would take a non-const contour as parameter instead, which is a shame because that function only reads from it...

And the question was, I was wondering, if a simple integer can be an index for both const and non const std::vectors, was there also a simple way in C++ to have something be a iterator for both const and non const std::list, but the answer turns out to be no...


Why can't you do this?



template<class Iter>
struct IntersectionStruct
{
typedef Iter IterType;

int x, y;
Iter Edge1, Edge2;
};






Template your intersect method, if it receives a const contour, it returns an IntersectionStruct<const_iterator>, otherwise it returns an IntersectionStruct<iterator>




template<class Contour>
struct SelectIntersectionStruct
{
typedef boost::mpl::if_<
boost::is_const<Contour>,
IntersectionStruct<std::list<Edge>::const_iterator>,
IntersectionStruct<std::list<Edge>::iterator>::type type;
};

template<class Contour>
SelectIntersectionStruct<Contour>::type
calculate_intersection(Contour& c)
{
typedef SelectIntersectionStruct::type IntersectionStruct;
typedef IntersectionStruct::IterType IterType;

IntersectionStruct intersection;
IterType edge1, edge2;

//do stuff

intersection.Edge1 = edge1;
intersection.Edge2 = edge2;
return intersection;
}





Note that I only used boost::mpl::if_ for convenience. If you don't have access to boost it's very easy to write this template manually.

I agree this can be ugly for someone not accustomed to meta-programming, but eh.. That's C++ for you :(

Share this post


Link to post
Share on other sites
Zipster    2365
The issue isn't with choosing whether to use an iterator or const_iterator when you already know whether the Contour you have right now is const or non-const, it's with storing the iterators. Conceptually, by storing regular iterators you're always providing non-const access to the Contour elements regardless of whether or not there's a time in the future you only want const access. Likewise, storing const_iterator's means forcing const access to the Contour elements even if there's a point in the future when you want to provide non-const access. Conceptually, the iterators are only supposed to reference elements in the Contour, not determine their access restrictions. An integer index itself can be const or non-const, and the const-ness of the accessed element will be determined by the const-ness of the Contour every single time you access it. The index alone can't be used to access the Contour, like with an iterator; you need to an actual Contour object/reference and an index 'i' to do Contour[i] every single time. Assuming that the Contour is a vector again, it's like saying that if you store a 'const int' index, you're only allowed const-access to the Contour, and if you stored a 'int' index, you're always allowed non-const access to the Contour.

Iterators to non-vectors aren't really meant to be stored in the same way as indexes to vectors, so the only real solution to this particular issue is to keep storing indexes, but use std::distance/std::advance and eat the O(n) conversion between indexes and iterators (as Sneftel posted so long ago).

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this