std::set using class pointers. How to control insert [duplicate] - c++11

#include <iostream>
#include <set>
using namespace std;
class StudentT {
public:
int id;
string name;
public:
StudentT(int _id, string _name) : id(_id), name(_name) {
}
int getId() {
return id;
}
string getName() {
return name;
}
};
inline bool operator< (StudentT s1, StudentT s2) {
return s1.getId() < s2.getId();
}
int main() {
set<StudentT> st;
StudentT s1(0, "Tom");
StudentT s2(1, "Tim");
st.insert(s1);
st.insert(s2);
set<StudentT> :: iterator itr;
for (itr = st.begin(); itr != st.end(); itr++) {
cout << itr->getId() << " " << itr->getName() << endl;
}
return 0;
}
In line:
cout << itr->getId() << " " << itr->getName() << endl;
It give an error that:
../main.cpp:35: error: passing 'const StudentT' as 'this' argument of 'int StudentT::getId()' discards qualifiers
../main.cpp:35: error: passing 'const StudentT' as 'this' argument of 'std::string StudentT::getName()' discards qualifiers
What's wrong with this code? Thank you!

The objects in the std::set are stored as const StudentT. So when you try to call getId() with the const object the compiler detects a problem, mainly you're calling a non-const member function on const object which is not allowed because non-const member functions make NO PROMISE not to modify the object; so the compiler is going to make a safe assumption that getId() might attempt to modify the object but at the same time, it also notices that the object is const; so any attempt to modify the const object should be an error. Hence compiler generates an error message.
The solution is simple: make the functions const as:
int getId() const {
return id;
}
string getName() const {
return name;
}
This is necessary because now you can call getId() and getName() on const objects as:
void f(const StudentT & s)
{
cout << s.getId(); //now okay, but error with your versions
cout << s.getName(); //now okay, but error with your versions
}
As a sidenote, you should implement operator< as :
inline bool operator< (const StudentT & s1, const StudentT & s2)
{
return s1.getId() < s2.getId();
}
Note parameters are now const reference.

Member functions that do not modify the class instance should be declared as const:
int getId() const {
return id;
}
string getName() const {
return name;
}
Anytime you see "discards qualifiers", it's talking about const or volatile.

Actually the C++ standard (i.e. C++ 0x draft) says (tnx to #Xeo & #Ben Voigt for pointing that out to me):
23.2.4 Associative containers
5 For set and multiset the value type
is the same as the key type. For map
and multimap it is equal to pair. Keys in an associative
container are immutable.
6 iterator of
an associative container is of the
bidirectional iterator category. For
associative containers where the value
type is the same as the key type, both
iterator and const_iterator are
constant iterators. It is unspecified
whether or not iterator and
const_iterator are the same type.
So VC++ 2008 Dinkumware implementation is faulty.
Old answer:
You got that error because in certain implementations of the std lib the set::iterator is the same as set::const_iterator.
For example libstdc++ (shipped with g++) has it (see here for the entire source code):
typedef typename _Rep_type::const_iterator iterator;
typedef typename _Rep_type::const_iterator const_iterator;
And in SGI's docs it states:
iterator Container Iterator used to iterate through a set.
const_iterator Container Const iterator used to iterate through a set. (Iterator and const_iterator are the same type.)
On the other hand VC++ 2008 Express compiles your code without complaining that you're calling non const methods on set::iterators.

Let's me give a more detail example. As to the below struct:
struct Count{
uint32_t c;
Count(uint32_t i=0):c(i){}
uint32_t getCount(){
return c;
}
uint32_t add(const Count& count){
uint32_t total = c + count.getCount();
return total;
}
};
As you see the above, the IDE(CLion), will give tips Non-const function 'getCount' is called on the const object. In the method add count is declared as const object, but the method getCount is not const method, so count.getCount() may change the members in count.
Compile error as below(core message in my compiler):
error: passing 'const xy_stl::Count' as 'this' argument discards qualifiers [-fpermissive]
To solve the above problem, you can:
change the method uint32_t getCount(){...} to uint32_t getCount() const {...}. So count.getCount() won't change the members in count.
or
change uint32_t add(const Count& count){...} to uint32_t add(Count& count){...}. So count don't care about changing members in it.
As to your problem, objects in the std::set are stored as const StudentT, but the method getId and getName are not const, so you give the above error.
You can also see this question Meaning of 'const' last in a function declaration of a class? for more detail.

Related

C++ STL; Iterate over class which contains STL container?

I have classes for linear algebra, specifically vectors and matrices. These contain among others std::vectors (or std::maps) as their 'data' fields.
Iterating over these in a range based for loop is easy. But I'd like to make these fields private and make iterating over my custom classes more natural, such that I can do range based for loops over the class itself.
I tried looking at the function definition of std::vector<>.begin() and such. Then I tried to implement it in such a way that all the iterator objects are forwaded from the std::vector<> fields, but to no avail. When I try to iterate over a constant instance of my class, with the following example;
int main() {
AlgebraLib::Vector A(4, true);
A[0] = 4;
A[1] = 5;
A[2] = 6;
A[3] = 7;
for (auto &&item : A) {
std::cout << item << std::endl;
}
const AlgebraLib::Vector B = A;
for (auto &&item : B) {
std::cout << item << std::endl;
}
return EXIT_SUCCESS;
}
... I get the following compiler error;
error: passing ‘const AlgebraLib::Vector’ as ‘this’ argument discards qualifiers [-fpermissive]
for (auto &&item : B) {
Basically, all iterators are defined like this:
std::vector<double>::iterator Vector::begin() {
return _VectorContents.begin();
}
std::vector<double>::iterator Vector::end() {
return _VectorContents.end();
}
std::vector<double>::reverse_iterator Vector::rbegin() {
return _VectorContents.rbegin();
}
std::vector<double>::reverse_iterator Vector::rend() {
return _VectorContents.rend();
}
std::vector<double>::const_iterator Vector::cbegin() const noexcept{
return _VectorContents.cbegin();
}
std::vector<double>::const_iterator Vector::cend() const noexcept{
return _VectorContents.cend();
}
std::vector<double>::const_reverse_iterator Vector::crbegin() const noexcept{
return _VectorContents.crbegin();
}
std::vector<double>::const_reverse_iterator Vector::crend() const noexcept{
return _VectorContents.crend();
}
And in my header Vector.hpp:
// Iterators
std::vector<double>::iterator begin();
std::vector<double>::iterator end();
std::vector<double>::reverse_iterator rbegin();
std::vector<double>::reverse_iterator rend();
std::vector<double>::const_iterator cbegin() const noexcept;
std::vector<double>::const_iterator cend() const noexcept;
std::vector<double>::const_reverse_iterator crbegin() const noexcept;
std::vector<double>::const_reverse_iterator crend() const noexcept;
I feel like I'm missing something here? I'm relatively new to C++, and I work at the moment with C++11.
The problem is that B is const, but the range-for loop only uses begin and end (not cbegin and cend) and your begin and end functions have no overloads that are const. This is a problem because only functions that are marked as const can be called on const objects.
The solution is simple: Just add such overloads
std::vector<double>::const_iterator begin() const;
std::vector<double>::const_iterator end() const;
The implementation of these overloads will be just the same as for the non-const functions.

std::shared_ptr and dlopen(), avoiding undefined behavior

dlopen() is a C function used for dynamically loading shared libraries at runtime. The pattern, in case you're not familiar, is thus:
Call dlopen("libpath", flag) to get a void *handle to the library
Call dlsym(handle, "object_name") to get a void *object to the thing you want from the library
Do what you want with object
Call dlclose (handle) to unload the library.
This is, in C++, a perfect use-case for the so-called aliasing constructor of std::shared_ptr. The pattern becomes:
Construct a std::shared_ptr<void> handle from dlopen("libpath", flag) that will call dlclose() when its destructor is called
Construct a std::shared_ptr<void> object from handle and dlsym(handle, "object_name")
Now we can pass object wherever we want, and completely forget about handle; when object's destructor is called, whenever that happens to be, dlclose() will be called automagically
Brilliant pattern, and it works beautifully. One small problem, though. The pattern above requires a cast from void* to whatever_type_object_is*. If "object_name" refers to a function (which most of the time it does, considering the use-case), this is undefined behavior.
In C, there is a hack to get around this. From the dlopen man page:
// ...
void *handle;
double (*cosine)(double);
// ...
handle = dlopen("libm.so", RTLD_LAZY);
// ...
/* Writing: cosine = double (*)(double)) dlsym(handle, "cos");
would seem more natural, but the C99 standard leaves
casting from "void *" to a function pointer undefined.
The assignment used below is the POSIX.1-2003 (Technical
Corrigendum 1) workaround; see the Rationale for the
POSIX specification of dlsym(). */
*(void **) (&cosine) = dlsym(handle, "cos");
// ...
which obviously works just fine, in C. But is there an easy way to do this with std::shared_ptr?
The pattern above requires a cast from void* to whatever_type_object_is*. If "object_name" refers to a function (which most of the time it does, considering the use-case), this is undefined behavior.
Well this is not entirely true, at least in C++ it is just conditionally-supported.
5.2.10.8 says:
Converting a function pointer to an object pointer type or vice versa is conditionally-supported. The meaning
of such a conversion is implementation-defined, except that if an implementation supports conversions in
both directions, converting a prvalue of one type to the other type and back, possibly with different cvqualification,
shall yield the original pointer value.
So assuming that what dlsym does internally is casting a function pointer to a void*, I believe that you are ok if you just cast it back to a function pointer.
Something like this?
struct dlib
{
public:
template<class T>
std::shared_ptr<T> sym(const char* name) const {
if (!handle) return {};
void* sym = dlsym(handle->get(), name);
if (!sym) return {};
return {reinterpret_cast<T*>(sym), handle};
}
// returns a smart pointer pointing at a function for name:
template<class Sig>
std::shared_ptr<Sig*> pfunc(const char* name) const {
if (!handle) return {};
void* sym = dlsym(handle->get(), name);
if (!sym) return {};
Sig* ret = 0;
// apparently approved hack to convert void* to function pointer
// in some silly compilers:
*reinterpret_cast<void**>(&ret) = sym;
return {ret, handle};
}
// returns a std::function<Sig> for a name:
template<class Sig>
std::function<Sig> function(const char* name) const {
// shared pointer to a function pointer:
auto pf = pfunc(name);
if (!pf) return {};
return [pf=std::move(pf)](auto&&...args)->decltype(auto){
return (*pf)(decltype(args)(args)...);
};
}
dlib() = default;
dlib(dlib const&)=default;
dlib(dlib &&)=default;
dlib& operator=(dlib const&)=default;
dlib& operator=(dlib &&)=default;
dlib(const char* name, int flag) {
void* h = dlopen(name, flag);
if (h)
{
// set handle to cleanup the dlopen:
handle=std::shared_ptr<void>(
h,
[](void* handle){
int r = dlclose(handle);
ASSERT(r==0);
}
);
}
}
explicit operator bool() const { return (bool)handle; }
private:
std::shared_ptr<void> handle;
};
I doubt that hack is needed. As #sbabbi noted, the round-trip to void* is conditionally supported. On a system using dlsym to return function pointers, it better be supported.
You can make a struct to have your pointer to function and handle to library:
template<typename T>
struct dlsymbol {
dlsymbol( const std::string &name, std::shared_ptr<void> handle ) :
m_handle( std::move( handle ) )
{
*(void **)(&m_func) = dlsym( handle.get(), name.c_str );
}
std::shared_ptr<void> m_handle;
T *m_func;
};
auto cosine = std::make_shared<dlsymbol<double(double)>>( "cos", handle );
auto d = cosine->m_func( 1.0 );
I did not compile it, but I think it is sufficient to show the idea.

an iterator that constructs a new object on dereference

I have a Visual Studio 2013 C++11 project where I'm trying to define an iterator. I want that iterator to dereference to an object, but internally it actually iterates over some internal data the object requires for construction.
class my_obj
{
public:
my_obj(some_internal_initialization_value_ v);
std::wstring friendly_name() const;
// ...
};
class my_iterator
: public boost::iterator_facade<
my_iterator,
my_obj,
boost::forward_traversal_tag>
{
// ...
private:
my_obj& dereference() const
{
// warning C4172: returning address of local variable or temporary
return my_obj(some_internal_initialization_value_);
}
};
int main( int argc, char* argv[])
{
my_container c;
for (auto o = c.begin(); o != c.end(); ++o)
printf( "%s\n", o->friendly_name().c_str() );
}
These internal values are unimportant implementation details to the user and I'd prefer not to expose them. How can I write the iterator that does this correctly? The alternative is that I would have to do something like this:
my_container c;
for (auto i = c.begin(); i != c.end(); ++i)
{
my_obj o(*i);
printf( "%s\n", o.friendly_name().c_str() );
}
From the boost page on iterator_facade, the template arguments are: derived iterator, value_type, category, reference type, difference_type. Ergo, merely tell it that references are not references
class my_iterator
: public boost::iterator_facade<
my_iterator,
my_obj,
boost::forward_traversal_tag,
my_obj> //dereference returns "my_obj" not "my_obj&"
See it working here: http://coliru.stacked-crooked.com/a/4b09ddc37068368b

Why can't I push a const pointer to std::vector?

Consider the piece of code:
class T;
void constructVector(const T* item)
{
std::vector<T*> v;
v.push_back(item);
}
I get an error with MSVC 2010 compiler:
error: C2664: 'void std::vector<_Ty>::push_back(_Ty &&)' : cannot
convert parameter 1 from 'const T *' to 'T *&&' with [
_Ty=T * ] Conversion loses qualifiers
I can see this particular conversion is illegal, but I don't believe my code is semantically wrong. I also believe there's push_back(const T&) variant, why isn't that matched to my call?
Because that's a vector of non-const pointers. It won't convert a const pointer to a non-const pointer. That would defeat the purpose of const.
I believe that the push_back(const T&) is not what you're looking for, because that makes the T object itself const, it does not change the type of T from (*) to (const *).
You could make the vector a vector of const pointers :
void constructVector(const T* item)
{
std::vector<const T*> v;
v.push_back(item);
}
Or you could change your function to take a non-const pointer :
void constructVector(T* item)
{
std::vector<T*> v;
v.push_back(item);
}
Drop const
void constructVector( const T* item);
or
Use:
void constructVector(const T* item)
{
std::vector<const T*> v;
v.push_back(item);
}

Migrating from VS to GCC: change in use of typename?

I'm building a large project on Debian 6.0.6 (with gcc 4.4.5) that was initially built in Microsoft VS (2008, I think).
What seems to be the problem is that when I declare a member as
typedef typename std::set<T>::iterator iterator, and then later use this iterator, gcc appears to interpret this as (const T*).
The part of the class containing the typename designation:
template <class entityType>
class entityArray
{
private: std::set<entityType> m_array;
public: typedef typename std::set<entityType>::iterator iterator;
...
public:
entityType* At( const char* name);
...
};
plus a few other classes that are needed for the discussion:
class entity
{
private:
entity* m_parent;
int m_ncid;
std::string m_name;
public:
entity () { m_ncid = 0; m_parent = NULL;}
virtual ~entity () {};
...
};
class attribute : public entity
{
public:
attribute(){};
virtual ~attribute(){};
};
class var : public entity
{
private:
entityArray<attribute> m_atts;
public:
var(){}
virtual ~var(){}
...
};
class dim : public entity
{
public:
dim() {};
virtual ~dim() {};
};
class group : public entity
{
private:
entityArray<var> m_vars;
entityArray<dim> m_dims;
...
public:
dim* DimAt( const char* dimname ) { return m_dims.At(dimname);}
};
Now an iterator is initialized through a call to the function DimAt which in turn calls At. The At function in the first class is defined as:
template <class entityType>
entityType* entityArray<entityType>::At( const char* name )
{
entityType dummy;
iterator iter;
entityType* ptr;
... define dummy ...
iter = m_array.find( dummy );
ptr = (iter != m_array.end()) ? &(*iter) : NULL;
return ptr;
}
Compiling the above produces
error: invalid conversion from const dim* to dim*., referring to &(*iter).
I realize that typename is required for declaring iterator, since the type is a dependent and qualified name, but I don't see why this substitution (const *) is being performed by the compiler. I would appreciate any help that you could provide. Thanks!
This has absolutely nothing to do with typename.
The standard allows std::set<T>::iterator and std::set<T>::const_iterator to be the same type, and with GCC the types are the same.
The reason is that modifying an element of a std::set e.g. by *iter = val might invalidate the ordering of the set elements, breaking the invariant that the elements of the set are always in order. By making the iterator type a constant iterator instead of a mutable iterator it's not possible to alter the element, preventing you from corrupting the set's ordering.
So with GCC's implementation, when you dereference the iterator using *iter you get a const entitType& and when you take its address using &*iter you get a const entityType*

Resources