const_pointer_cast of a std::set iterator - c++

I've got a bit of a conundrum here. I am writing a mesh (grid) generator. In doing so, I need to obtain a list of lower dimensional elements from higher dimensional elements (i.e. a "triangle" element is composed of 3 "line" elements). I need the list of unique line elements and I need the parent element to store a pointer to each of its unique child elements.
To this end, I create a std::set of smart pointers to the child elements and try to insert each child element to the list. Every time I try to insert an element I ask for a pair containing an iterator to the pre-existing element and a boolean specifying whether or not the element has been inserted.
The problem is that std::set (and map) return constant iterators to the pre-existing element. However, I need to give the parent element of the element that I failed to insert a non-constant pointer to the pre-existing element. So I use const_pointer_cast<> to cast the constant iterator to a non-constant one.
Is there a better way of going about this? Doing a const_pointer_cast() is probably not ideal. Is there something that I should be using for this application instead of std::set?
Edit: here's the function:
template<class T1, class T2>
int getUniqueSubelements(std::set<std::shared_ptr<T1>, elGreater>& elements,
std::set<std::shared_ptr<T2>, elGreater>& childels)
{
typedef std::shared_ptr<T2> child_ptr;
std::pair<typename std::set<child_ptr, elGreater>::iterator, bool> ret;
for (auto parit = elements.begin(); parit != elements.end(); ++parit)
{
(*parit)->generateChildren();
const std::vector<child_ptr>& children = (*parit)->getChildren();
for (int i = 0; i < children.size(); i++)
{
ret = childels.insert(children[i]);
if (ret.second == false)
{
child_ptr temp = std::const_pointer_cast<T2>(*ret.first);
bool orient = elOrientation(children[i], temp);
children[i]->swapInParent(temp, orient);
}
}
}
return 1;
}

No cast at all is needed here.
It's true that std::set<K, Comp>::iterator is the same as std::set<K, Comp>::const_iterator, and that iterator::operator* returns a const K&.
But in your case, this means that the type of *ret.first is const std::shared_ptr<T2>&. This is the analogue of (T2* const), not (const T2*): you can't modify the smart pointer, but you can modify the actual stored object.
So assuming elOrientation is something like
template <typename T>
bool elOrientation(std::shared_ptr<T>, std::shared_ptr<T>);
you can just call elOrientation(children[i], *ret.first).

Related

std::multiset define comparator for insertion and comparison

I'm using a std::multiset of pointers to objects to implement Z-ordering in my game, so I don't need to sort the structure on each insertion. I use a comparator for insertion by the object's depth:
struct rendererComparator
{
bool operator ()(const Renderable* r1, const Renderable* r2) const
{
return r1->depth < r2->depth;
}
};
std::multiset<Renderable*, rendererComparator> m_Renderables;
However when it comes to erasing an element in the multiset, the call to erase removes all elements which have the same depth which is undesirable. I tried the suggestions in this question: In std::multiset is there a function or algorithm to erase just one sample (unicate or duplicate) if an element is found but
auto iterator = m_Renderables.find(renderable);
if (iterator != m_Renderables.end())
{
m_Renderables.erase(renderable);
}
Still erases all the elements with the same depth because of the comparator.
Is it possible to define 2 comparators for std::multiset without boost? (How can I set two kind of comparator (one for insert, one for find) on this multiset?) One for insertion and one for comparison?
Thanks
Edit: Jignatious pointed out that I wasn't erasing the iterator (typo by me). I solved it by using std::find_if
auto iterator = std::find_if(m_Renderables.begin(), m_Renderables.end(), [renderable](const Renderable* r1) { return r1 == renderable; });
if (iterator != m_Renderables.end())
{
m_Renderables.erase(iterator);
}
The problem is on this line:
m_Renderables.erase(renderable);
which erases all elements with the same value.
You need to erase with the iterator from the find() function call instead. That will erase the single element that the iterator points to:
m_Renderables.erase(iterator);
Note that std::multiset::find() returns an iterator pointing to the lower bound (or first) of the elements which is searched in the multiset if it exists, otherwise the one past the end element iterator.
Instead of multiset, you can use std::set with comparer like this:
struct Element
{
int value;
Element(int v)
{
value = v;
}
bool operator() (Element* const& left, Element* const& right) const
{
if (left->value == right->value)
return (left < right);
return left->value < right->value;
}
};
It will store multiple values like multimap, but without 'erase all' on erase, without replace same values on insert and with proper find by reference.
std::set<Element*, Element> set;
set.insert(new Element(10));
auto last = new Element(10);
set.insert(last); // 10 10 like in multiset
set.erase(last); // will delete proper references

How to insert objects into std::vector?

I am finding trouble inserting an object into a std::vector, following the example of what I'm trying to do:
//in SomeClass.cpp
void SomeClass::addItem(int32_t &position, OtherClass &value)
{
vectorOtherClass.insert(position,valvalue);
}
however I get the following error when trying to compile the program:
error: no matching function for call to ‘std::vector::insert(int32_t&, OtherClass&)’
vectorOtherClass.insert(position,value);
______________________^
the vector definition in SomeClass.h is:
private:
std::vector<OtherClass> vectorOtherClass;
How can I properly insert an object into a vector in C ++?
And one last question, are the objects stored by reference or by copy within the vector?
Like the error says, there is no insert function with int parameter. See:
single element (1)
iterator insert (iterator position, const value_type& val);
fill (2)
void insert (iterator position, size_type n, const value_type& val);
range (3)
template <class InputIterator>
void insert (iterator position, InputIterator first, InputIterator last);
You can find an example here http://www.cplusplus.com/reference/vector/vector/insert/
int main ()
{
std::vector<int> myvector (3,100);
std::vector<int>::iterator it;
it = myvector.begin();
it = myvector.insert ( it , 200 );
myvector.insert (it,2,300);
// "it" no longer valid, get a new one:
it = myvector.begin();
std::vector<int> anothervector (2,400);
myvector.insert (it+2,anothervector.begin(),anothervector.end());
int myarray [] = { 501,502,503 };
myvector.insert (myvector.begin(), myarray, myarray+3);
std::cout << "myvector contains:";
for (it=myvector.begin(); it<myvector.end(); it++)
std::cout << ' ' << *it;
std::cout << '\n';
return 0;
}
According to the method's reference, the insert method takes the following parameters:
position
Position in the vector where the new elements are inserted.
iterator is a member type, defined as a random access iterator type that points to elements.
val
Value to be copied (or moved) to the inserted elements.
Member type value_type is the type of the elements in the container, defined in deque as an alias of its first template parameter (T).
Note that position is not an integral value, but rather an iterator. C++ use iterators a lot, since many operations are fairly efficient when you have one.
In particular, you can add vector iterators and numbers to get the iterator to the correct position, so you can do something like:
vectorOtherClass.insert(vectorOtherClass.begin() + position, value);
This does not apply to other containers such as std::list. Also, you should make sure to check position is within the bounds of the vector (0 <= position < vectorOtherClass.size()). Ideally, position should be unsigned to ensure the lower bound.
Lastly, elements are added to std::vectors as copies. Vectors use an array internally, so values are copied into it. The array is resized (copied and replaced) as needed.
Refer to: http://en.cppreference.com/w/cpp/container/vector/insert
You need to pass in an interator, so use begin and offset the position from there. No need to pass in ints by ref unless your function is going to change them. Consider checking for buffer overflow.
void SomeClass::addItem(int32_t position, const OtherClass &value)
{
assert(position < vectorOtherClass.size());
assert(position >= 0);
vectorOtherClass.insert(vectorOtherClass.begin()+position, value);
}

How to initialize std::vector of std:pair of fixed length with default values?

Assuming a struct containing a std::vector with items of type std::pair.
struct block {
std::vector<std::pair<key_t, value_t>> items;
block(const Data& d) : items()
{
items = std::vector<std::pair<key_t, value_t>>(d.size_);
}
};
Later in the code, I assign a value to the vector at position 0:
block<key_t, value_t> b(*this);
std::pair<key_t, value_t> item = std::pair<key_t, value_t>(k, v);
b.items[0] = item;
Later in the code, I want to iterate over the vector and expect &bucket_item != NULL to be true only at position 0, because I only assigned a value at this position. In fact, &bucket_item != NULL is always true.
for (std::pair<key_t, value_t> item : b.items)
{
if (&item != NULL)
{
...
}
}
I am not able to initialize the vector with NULL values like so:
items = std::vector<std::pair<key_t, value_t>>(d.size_, NULL);
How to solve this?
It seems like you have Java background, C++ is a bit different.
items = std::vector<std::pair<key_t, value_t>>(d.size_);
items has already been initialized with its default constructor. The line above creates another default initialized container and assigns it to items, which is unnecessary.
In:
b.items[0] = item;
You need to make sure that the vector is big enough, because it does not allocate elements for you in operator[]. Either do b.items.push_front/back(item) or insert it at a specific position using vector::insert, e.g. b.items.insert(b.items.begin(), item). push_xxx and insert do allocate memory for new elements.
When you do
for (std::pair<key_t, value_t> item : b.items)
{
if (&item != NULL)
It iterates over all existing elements in the container. item is stored by value (unlike Java) in the container, so that it exists at a non-NULL address and cannot be possibly be equal to NULL.
However, expression for(std::pair<key_t, value_t> item : b.items) creates a copy of each element on the stack (also at non-NULL address), you probably want that to be for(std::pair<key_t, value_t>& item : b.items) to just refer to the element rather than copy it (note the ampersand symbol on the left of items). If you do not intend to modify item inside the loop, you may like to use const qualifier as well to make your intent clear to the reader of the code. e.g. for(std::pair<key_t, value_t> const& item : b.items).
And because you use C++11 anyway, you do not have to spell out the type of the container element, just use auto:
for (auto const& item : b.items)
// do something to item
When you create an std::vector with a length len like this std::vector<T> tVec(len), you are creating a vector with len default-constructed objects of type T. If you want to represent a null value, you will need to resort to one of the following ways:
Use a sentinel value of T to denote an invalid value.
Use a (smart-)pointer to T and use a nullptr as the natural invalid value.
Wrap a class around T which contains a bool marking it as invalid.
The last option is provided by boost::optional. Here's a rewrite of your code using it:
struct block {
using OptionalPair_t = boost::optional<std::pair<key_t, value_t>>;
std::vector<OptionalPair_t> items;
block(const Data& d) : items(d.size_)
{
}
};
Since boost::optional is contextually convertible to bool, you can do this:
for (auto& item : b.items)
{
if (item)
{
...
}
}
I think your mixing here a few definitions.
an element of std::vector cannot be NULL, because it's not a pointer. and it defenilty exists.
int arr[] = {1};, can arr[0] be null? of course not. why would it be ? it's a real integer who seat somwhere in the memory which is not null.
if you're iterating over a std::vector elements, that means they exist, so they can not be null.
NULL, or better nullptr, can be used to initialize a pointer value, but makes no sense to initialize, for example, an std::string or std::pair<std::string, int> with it.
If you want your vector to be empty, you can use:
std::vector<std::pair<key_t, value_t>> items;
othwerwise, if you want n default constructed std::pairs you can use:
std::vector<std::pair<key_t, value_t>> items(n);
Pair are really a comination of values. So you have to define which combination of values you consider as being the NULL equivalent for your pairs.
You can then initialize your vector with the following constructor:
std::vector<std::pair<key_t, value_t>> items(d.size_, make_pair(0,0)) ;
You just have to replace 0 with the neutral values for key_t and value_t.
Please note that vectors really contain values, and pairs are values. So there is no NULL pointers that would show absence of values.

How to use a method that has a pointer to an array for an argument?

I would like to make a method that has for argument a pointer to the first element of an array, and that then cycles through the array looking for something...
void MyClass::myMethod(T *x)
{
// I know that *x points to the first entry in a properly defined array.
// I want to cycle through this array, and try to find a member T of that array that
// meets certain criteria.
// I would then like to store a pointer to this T that meets my criteria.
}
You also need to pass the size of the array explicitly. Then you can do:
for(T *current = x; current < x + count; ++current) {
// do something with current, compare or whatever
}
An alternative would be to use indexing notation, like below. Which one is better mainly depends on how exactly you want to access the data later on.
for(int i = 0; i < count; ++i) {
// current element is x[i]
// pointer to current element is then x+i, or &x[i]
}
Usually you are better off using standard array containers, implementing your algorithms using iterators. Your function then takes two iterators defining the range on which it should operate, basically like this:
template<typename Iterator>
void doSomething(Iterator begin, Iterator end) {
for(; begin != end; ++begin) {
// current element is *begin
}
}
instead of using pointers. Create a vector and use find_if to return the iterator(position ) of the element of interest and do your processing.

How do I access objects in a linked-list in C++

I'm very use to working with arrays and vectors, but now I'm playing with some STD::lists, as well as a custom list class I made.
Let's say I have a simple class, Stock.
//stock.h
class Stock{
public:
Stock(); //default constructor
Stock(string, double); //overloaded constructor
void setSymbol(string); //sets stock symbol
void setPrice(double);
string getSymbol();
double getPrice();
private:
string symbol;
double price;
};
Now in a separate file I have my int main to test.
#include "stock.h"
#include <list>
int main(){
list<Stock> portfolio;
Stock Google("GOOG", 500);
Stock Apple("APPL", 300);
Stock Chipotle("CMG", 200);
portfolio.push_back(Google);
portfolio.push_back(Apple);
portfolio.push_back(Chipotle);
}
Now if this was a vector or array, I would have no problem, I'm just completely loss on the linked-list equivalent of the following:
for(int i=0; i <portfolio.size(); i++){
portfolio[i].getSymbol();
portfolio[i].getPrice();
}
Or something along those lines...I have no lecture/training in Linked-Lists so I'm really trying to do my best in teaching myself--but I'm stuck on basic manipulation. I'm using STL::list right now, but really trying to make my own class as well.
for(int i= portfolio.begin(); i <portfolio.size(); i++)
If this worked for a std::vector, it was only by sheer accident. I have no idea how that might have worked for a vector.
std::any_stl_container::begin() returns an object called an "iterator". Specifically, an object of type std::any_stl_container::iterator. An iterator is kind of like a generalized pointer: it refers to an element in an STL container.
The iterator returned by begin is the iterator that references the first element in the list. You can move iterators around like pointers. For example:
std::list<Stock> portfolio;
...
std::list<Stock>::iterator currElem = portfolio.begin();
++currElem; //Now points to the second element in the list.
Stock &secondStock = *currElem;
In order to iterate over all of the elements in a list, you need two iterators: the first one, and the iterator for the element after the last element in the list. Fortunately, this is returned by the std::any_stl_container::end() function. So, your loop should look like:
typedef std::list<Stock>::iterator StockIt;
for(StockIt i = portfolio.begin(); i != portfolio.end(); ++i) /* Prefer pre-increment with iterators */
{
i->getSymbol();
i->getPrice();
}
This will work for any of the usual STL containers.
You have iterators as begin() and end() for std::list also:
for (list<stock>::iterator it = portfolio.begin() ; it != portfolio.end(); it++)
{
// use 'it' to access the current element (i.e. *it)
}
You have to use iterators. Every STL container has them, even vector. They behave similarly to pointers; they can be dereferenced with * or ->, they can be incremented, they can be compared.
for( list<Stock>::iterator it = portfolio.begin(); it != portfolio.end(); it++ ){
it->getSymbol();
it->getPrice();
}
An object of type std::list is very inefficient if you try to compute its length, because such an operation would have linear time.
Therefore with lists, you cannot compare iterators with the < and > operators. It is also ill-advised to try to get the nth element with the operator [].
Also, you should not mix begin() and size() which are respectively an iterator and an integer.
The proper way to iterate over an std::list is to use the pair of iterators begin() and end() and to increment your iterator until it reaches the end().
So, two ways:
for(std::list<stock>::const_iterator i = my_list.cbegin(); i != my_list.cend(); ++i)
{
// access a “const stock &” object via *i
}
or
for(std.:list<stock>::iterator i = my_list.begin(); i != my_list.end(); ++i)
{
// access a “stock &” object via *i
}