Vector of structs with const members? - c++

Let's say I have
#include <string>
#include <vector>
using namespace std;
struct Student
{
const string name;
int grade;
Student(const string &name) : name(name) { }
};
How do I, then, keep a vector of students?
int main()
{
vector<Student> v;
// error C2582: 'operator =' function is unavailable in 'Student'
v.push_back(Student("john"));
}
Is there even a way to do this, or must I allocate all the students on the heap, and store a pointer to each of them instead?

You can't. Your type violates the "Assignable" requirement for standard containers.
ISO/IEC 14882:2003 23.1 [lib.container.requirements] / 3:
The type of objects stored in these components must meet the requirements of CopyConstructible
types (20.1.3), and the additional requirements of Assignable types.
From table 64 (Assignable requirements):
In Table 64, T is the type used to instantiate the container, t is a value of T, and u is a value of (possibly const) T.
expression: t = u; return type: T; post-condition: t is equivalent to u
In theory, a std::vector equivalent could choose to do destruction and copy construction in all cases, but that's not the contract that has been chosen. If reallocation isn't required, then using the contained type's assignment operator for things like vector::operator= and vector::assign might be significantly more efficient.

The simple answer is: you can't. If you have const member variables, then the compiler can't supply a default copy-assignment operator. However, many of the operations that std::vector provides need to make assignments, and therefore require a (public) copy-assignment operator.
Your options are:
Make name non-const.
Write your own copy-assignment operator, and think of a way to deal with "copying" a const member.

A vector often needs to move elements around. Every time a vector needs to grow when you call push_back() it reallocates memory to keep itself contiguous, and copies all the existing elements into the new space. Also if you call insert() or remove() elements must
be shifted. For vector to be able to do all that the elements must be copy-assignable, which means that the type you store in the vector must have the assignment operator defined.
Generally, if you define a class, the compiler will generate the assignment operator for that class for you. However, there are cases when the compiler is unable to do that. One of these cases is when the class has constant members (note that pointers-to-const are ok).
So, in your case, the problem is the const string name. It prevents the compiler from generating operator=(), which in turn prevents vector from compiling, even though you do not actually use assignment on its elements yourself.
One solution is to make name non-const. The other is to write your own Student::operator=(), in some way that makes sense. The third way is, as you have pointed out, to use a vector of pointers rather than a vector of objects. But then you have to handle their allocation and de-allocation.
P.S. The other case when the compiler cannot generate operator= is when your class has members that are references.

Elements of vectors must be copy-assignable, which your Student struct isn't because of the const member. Simply use string name instead of const string name.
Unless you have a specific requirement, constant members in classes are seldom useful. If you want to prevent changes to the member, make it private and add a public getter function.

Related

C++ Error: no matching function for call - when used in class constructor [duplicate]

See also
C++ standard list and default-constructible types
Not a major issue, just annoying as I don't want my class to ever be instantiated without the particular arguments.
#include <map>
struct MyClass
{
MyClass(int t);
};
int main() {
std::map<int, MyClass> myMap;
myMap[14] = MyClass(42);
}
This gives me the following g++ error:
/usr/include/c++/4.3/bits/stl_map.h:419: error: no matching function for call to ‘MyClass()’
This compiles fine if I add a default constructor; I am certain it's not caused by incorrect syntax.
This issue comes with operator[]. Quote from SGI documentation:
data_type& operator[](const key_type& k) - Returns a reference to the object
that is associated with a particular
key. If the map does not already
contain such an object, operator[]
inserts the default object
data_type().
If you don't have default constructor you can use insert/find functions.
Following example works fine:
myMap.insert( std::map< int, MyClass >::value_type ( 1, MyClass(1) ) );
myMap.find( 1 )->second;
Yes. Values in STL containers need to maintain copy semantics. IOW, they need to behave like primitive types (e.g. int) which means, among other things, they should be default-constructible.
Without this (and others requirements) it would be needlessly hard to implement the various internal copy/move/swap/compare operations on the data structures with which STL containers are implemented.
Upon reference to the C++ Standard, I see my answer was not accurate. Default-construction is, in fact, not a requirement:
From 20.1.4.1:
The default constructor is not
required. Certain container class
member function signatures specify the
default constructor as a default
argument. T() must be a well-defined
expression ...
So, strictly speaking, your value type only needs to be default constructible if you happen to be using a function of the container that uses the default constructor in its signature.
The real requirements (23.1.3) from all values stored in STL containers are CopyConstructible and Assignable.
There are also other specific requirements for particular containers as well, such as being Comparable (e.g. for keys in a map).
Incidentally, the following compiles with no error on comeau:
#include <map>
class MyClass
{
public:
MyClass(int t);
};
int main()
{
std::map<int, MyClass> myMap;
}
So this might be a g++ problem.
Check requirements of stored type of the stl::map. Many stl collection require that stored type contains some specific properties (default constructor, copy constructor, etc.).
Constructor without arguments is needed by the stl::map, because it's used, when operator[] is invoked with the key, which hasn't already been kept by the map. In this case the operator[] inserts the new entry consisting of the new key and value constructed using parameterless constructor. And this new value is then returned.
assume you have the following
class Person
{
public:
Person(int age) :age(age){}
Person() {} // default ctor
int age;
};
map<int, Person> m;
// accessing not-existent key, results in assigning default value to that key
m[10];
// creates default object for key:20 first then assigns age
m[20].age = 32;
what should happen if you wanna assign age for a nonexistent key?
for languages with null type such as javascript the map returns null and its is up to user to check for it before accessing the object or its internal fields.
c++ went a different approach and creates the Person using default constructor so the null is avoided all together
Check if:
You forgot the ';' after class declaration.
MyType should've been declared accordingly.
No default constructor there...
The std::map declaration seems correct, I think.
Most likely because std::pair requires it. std::pair holds two values using value semantics so you need to be able to instantiate them without parameters. So the code uses std::pair in various places to return the map values to the caller and this is commonly done by instantiating an empty pair and assigning the values into it before returning the local pair.
You could get around this with smart pointers using a map<int, smartptr<MyClass> > but that adds the overhead of checking for null pointers.

declaring a map with its value type is a class [duplicate]

See also
C++ standard list and default-constructible types
Not a major issue, just annoying as I don't want my class to ever be instantiated without the particular arguments.
#include <map>
struct MyClass
{
MyClass(int t);
};
int main() {
std::map<int, MyClass> myMap;
myMap[14] = MyClass(42);
}
This gives me the following g++ error:
/usr/include/c++/4.3/bits/stl_map.h:419: error: no matching function for call to ‘MyClass()’
This compiles fine if I add a default constructor; I am certain it's not caused by incorrect syntax.
This issue comes with operator[]. Quote from SGI documentation:
data_type& operator[](const key_type& k) - Returns a reference to the object
that is associated with a particular
key. If the map does not already
contain such an object, operator[]
inserts the default object
data_type().
If you don't have default constructor you can use insert/find functions.
Following example works fine:
myMap.insert( std::map< int, MyClass >::value_type ( 1, MyClass(1) ) );
myMap.find( 1 )->second;
Yes. Values in STL containers need to maintain copy semantics. IOW, they need to behave like primitive types (e.g. int) which means, among other things, they should be default-constructible.
Without this (and others requirements) it would be needlessly hard to implement the various internal copy/move/swap/compare operations on the data structures with which STL containers are implemented.
Upon reference to the C++ Standard, I see my answer was not accurate. Default-construction is, in fact, not a requirement:
From 20.1.4.1:
The default constructor is not
required. Certain container class
member function signatures specify the
default constructor as a default
argument. T() must be a well-defined
expression ...
So, strictly speaking, your value type only needs to be default constructible if you happen to be using a function of the container that uses the default constructor in its signature.
The real requirements (23.1.3) from all values stored in STL containers are CopyConstructible and Assignable.
There are also other specific requirements for particular containers as well, such as being Comparable (e.g. for keys in a map).
Incidentally, the following compiles with no error on comeau:
#include <map>
class MyClass
{
public:
MyClass(int t);
};
int main()
{
std::map<int, MyClass> myMap;
}
So this might be a g++ problem.
Check requirements of stored type of the stl::map. Many stl collection require that stored type contains some specific properties (default constructor, copy constructor, etc.).
Constructor without arguments is needed by the stl::map, because it's used, when operator[] is invoked with the key, which hasn't already been kept by the map. In this case the operator[] inserts the new entry consisting of the new key and value constructed using parameterless constructor. And this new value is then returned.
assume you have the following
class Person
{
public:
Person(int age) :age(age){}
Person() {} // default ctor
int age;
};
map<int, Person> m;
// accessing not-existent key, results in assigning default value to that key
m[10];
// creates default object for key:20 first then assigns age
m[20].age = 32;
what should happen if you wanna assign age for a nonexistent key?
for languages with null type such as javascript the map returns null and its is up to user to check for it before accessing the object or its internal fields.
c++ went a different approach and creates the Person using default constructor so the null is avoided all together
Check if:
You forgot the ';' after class declaration.
MyType should've been declared accordingly.
No default constructor there...
The std::map declaration seems correct, I think.
Most likely because std::pair requires it. std::pair holds two values using value semantics so you need to be able to instantiate them without parameters. So the code uses std::pair in various places to return the map values to the caller and this is commonly done by instantiating an empty pair and assigning the values into it before returning the local pair.
You could get around this with smart pointers using a map<int, smartptr<MyClass> > but that adds the overhead of checking for null pointers.

Move constructors and `std::array`

According to N3485 §23.3.2.2:
(...) the implicit move constructor and move assignment operator for array require that T be MoveConstructible or MoveAssignable, respectively.
So, std::array supports move semantics if the type of its elements does. Great!
However, what does this really mean? I tend to picture this type as a safer version of an array providing an STL-compliant interface but, if this is true, then how can an std::array move-construct its elements? Can I do the same with an ordinary array?
However, what does this really mean?
It means that, if the element type is movable, then so is the array type.
std::array<movable, 42> move_from = {...};
std::array<movable, 42> move_to = std::move(move_from); // moves all the elements
I tend to picture this type as a safer version of an array providing an STL-compliant interface
Not really. It's a wrapper for an array, giving it the same semantics as an aggregate class - including the ability to copy and move it.
how can an std::array move-construct its elements?
In exactly the same way as any other aggregate. Its implicit move-constructor will move-construct all its members, including the elements of any member arrays.
Can I do the same with an ordinary array?
Only if you wrap it in a class type, as std::array does.
Moving a std::array is different from moving a std::vector. When moving one std::vector into another, it's (sometimes*) possible to simply re-target the internal pointers and avoid manipulating the elements at all.
With std::array, this is of course not possible - its elements have automatic storage duration, they are literally contained inside the object. However, each individual one of them can still be moved, and that's what the move operations on std::array do**.
* Assuming the allocators are compatible and don't prohibit this operation
** That's also what you get with std::vector when the buffer can't just be re-owned by the destination vector.
The default move constructor for a (non-union) class performs a member-wise move. Moving a raw array data member means moving each of the array's elements, see [class.copy]/15.
Therefore, you can move a raw array by putting it inside a class:
struct wrap
{
std::string arr[25];
};
auto w = wrap();
auto m = std::move(w); // moves the 25 `std::string`s
You can also manually invoke the move constructor of the elements, for example:
std::string a[3] = { /*...*/ };
std::string b[3] = {std::move(a[0]), std::move(a[1]), std::move(a[2])};
It is not specified if std::array contains a raw array. However, it does contain data members of the value_type, since it's guaranteed to be an aggregate. Those data members will be moved as described above when invoking the move constructor.
If the data members of a std::array are not MoveConstructible, instantiating its move constructor will fail.
You can do it using "placement new". You'll find plenty of questions on placement new already answered with many further details.
This one looks like it has a complete example:
Why is this code trying to call the copy constructor?

Trying to store an object in an array but then how to call that object's methods?

I'm not a very experienced c++ coder and this has me stumped. I am passing a object (created elsewhere) to a function, I want to be able to store that object in some array and then run through the array to call a function on that object. Here is some pseudo code:
void AddObject(T& object) {
object.action(); // this works
T* objectList = NULL;
// T gets allocated (not shown here) ...
T[0] = object;
T[0].action(); // this doesn't work
}
I know the object is passing correctly, because the first call to object.action() does what it should. But when I store object in the array, then try to invoke action() it causes a big crash.
Likely my problem is that I simply tinkered with the .'s and *'s until it compiled, T[0].action() compliles but crashes at runtime.
The simplest answer to your question is that you must declare your container correctly and you must define an appropriate assigment operator for your class. Working as closely as possible from your example:
typedef class MyActionableClass T;
T* getGlobalPointer();
void AddInstance(T const& objInstance)
{
T* arrayFromElsewhere = getGlobalPointer();
//ok, now at this point we have a reference to an object instance
//and a pointer which we assume is at the base of an array of T **objects**
//whose first element we don't mind losing
//**copy** the instance we've received
arrayFromElsewhere[0] = objInstance;
//now invoke the action() method on our **copy**
arrayFromElsewhere[0].action();
}
Note the signature change to const reference which emphasizes that we are going to copy the original object and not change it in any way.
Also note carefully that arrayFromElsewhere[0].action() is NOT the same as objInstance.action() because you have made a copy — action() is being invoked in a different context, no matter how similar.
While it is obvious you have condensed, the condensation makes the reason for doing this much less obvious — specifying, for instance, that you want to maintain an array of callback objects would make a better case for “needing” this capability. It is also a poor choice to use “T” like you did because this tends to imply template usage to most experienced C++ programmers.
The thing that is most likely causing your “unexplained” crash is that assignment operator; if you don't define one the compiler will automatically generate one that works as a bitwise copy — almost certainly not what you want if your class is anything other than a collection of simple data types (POD).
For this to work properly on a class of any complexity you will likely need to define a deep copy or use reference counting; in C++ it is almost always a poor choice to let the compiler create any of ctor, dtor, or assignment for you.
And, of course, it would be a good idea to use standard containers rather than the simple array mechanism you implied by your example. In that case you should probably also define a default ctor, a virtual dtor, and a copy ctor because of the assumptions made by containers and algorithms.
If, in fact, you do not want to create a copy of your object but want, instead, to invoke action() on the original object but from within an array, then you will need an array of pointers instead. Again working closely to your original example:
typedef class MyActionableClass T;
T** getGlobalPointer();
void AddInstance(T& objInstance)
{
T** arrayFromElsewhere = getGlobalPointer();
//ok, now at this point we have a reference to an object instance
//and a pointer which we assume is at the base of an array of T **pointers**
//whose first element we don't mind losing
//**reference** the instance we've received by saving its address
arrayFromElsewhere[0] = &objInstance;
//now invoke the action() method on **the original instance**
arrayFromElsewhere[0]->action();
}
Note closely that arrayFromElsewhere is now an array of pointers to objects instead of an array of actual objects.
Note that I dropped the const modifier in this case because I don’t know if action() is a const method — with a name like that I am assuming not…
Note carefully the ampersand (address-of) operator being used in the assignment.
Note also the new syntax for invoking the action() method by using the pointer-to operator.
Finally be advised that using standard containers of pointers is fraught with memory-leak peril, but typically not nearly as dangerous as using naked arrays :-/
I'm surprised it compiles. You declare an array, objectList of 8 pointers to T. Then you assign T[0] = object;. That's not what you want, what you want is one of
T objectList[8];
objectList[0] = object;
objectList[0].action();
or
T *objectList[8];
objectList[0] = &object;
objectList[0]->action();
Now I'm waiting for a C++ expert to explain why your code compiled, I'm really curious.
You can put the object either into a dynamic or a static array:
#include <vector> // dynamic
#include <array> // static
void AddObject(T const & t)
{
std::array<T, 12> arr;
std::vector<T> v;
arr[0] = t;
v.push_back(t);
arr[0].action();
v[0].action();
}
This doesn't really make a lot of sense, though; you would usually have defined your array somewhere else, outside the function.

Why does the C++ map type argument require an empty constructor when using []?

See also
C++ standard list and default-constructible types
Not a major issue, just annoying as I don't want my class to ever be instantiated without the particular arguments.
#include <map>
struct MyClass
{
MyClass(int t);
};
int main() {
std::map<int, MyClass> myMap;
myMap[14] = MyClass(42);
}
This gives me the following g++ error:
/usr/include/c++/4.3/bits/stl_map.h:419: error: no matching function for call to ‘MyClass()’
This compiles fine if I add a default constructor; I am certain it's not caused by incorrect syntax.
This issue comes with operator[]. Quote from SGI documentation:
data_type& operator[](const key_type& k) - Returns a reference to the object
that is associated with a particular
key. If the map does not already
contain such an object, operator[]
inserts the default object
data_type().
If you don't have default constructor you can use insert/find functions.
Following example works fine:
myMap.insert( std::map< int, MyClass >::value_type ( 1, MyClass(1) ) );
myMap.find( 1 )->second;
Yes. Values in STL containers need to maintain copy semantics. IOW, they need to behave like primitive types (e.g. int) which means, among other things, they should be default-constructible.
Without this (and others requirements) it would be needlessly hard to implement the various internal copy/move/swap/compare operations on the data structures with which STL containers are implemented.
Upon reference to the C++ Standard, I see my answer was not accurate. Default-construction is, in fact, not a requirement:
From 20.1.4.1:
The default constructor is not
required. Certain container class
member function signatures specify the
default constructor as a default
argument. T() must be a well-defined
expression ...
So, strictly speaking, your value type only needs to be default constructible if you happen to be using a function of the container that uses the default constructor in its signature.
The real requirements (23.1.3) from all values stored in STL containers are CopyConstructible and Assignable.
There are also other specific requirements for particular containers as well, such as being Comparable (e.g. for keys in a map).
Incidentally, the following compiles with no error on comeau:
#include <map>
class MyClass
{
public:
MyClass(int t);
};
int main()
{
std::map<int, MyClass> myMap;
}
So this might be a g++ problem.
Check requirements of stored type of the stl::map. Many stl collection require that stored type contains some specific properties (default constructor, copy constructor, etc.).
Constructor without arguments is needed by the stl::map, because it's used, when operator[] is invoked with the key, which hasn't already been kept by the map. In this case the operator[] inserts the new entry consisting of the new key and value constructed using parameterless constructor. And this new value is then returned.
assume you have the following
class Person
{
public:
Person(int age) :age(age){}
Person() {} // default ctor
int age;
};
map<int, Person> m;
// accessing not-existent key, results in assigning default value to that key
m[10];
// creates default object for key:20 first then assigns age
m[20].age = 32;
what should happen if you wanna assign age for a nonexistent key?
for languages with null type such as javascript the map returns null and its is up to user to check for it before accessing the object or its internal fields.
c++ went a different approach and creates the Person using default constructor so the null is avoided all together
Check if:
You forgot the ';' after class declaration.
MyType should've been declared accordingly.
No default constructor there...
The std::map declaration seems correct, I think.
Most likely because std::pair requires it. std::pair holds two values using value semantics so you need to be able to instantiate them without parameters. So the code uses std::pair in various places to return the map values to the caller and this is commonly done by instantiating an empty pair and assigning the values into it before returning the local pair.
You could get around this with smart pointers using a map<int, smartptr<MyClass> > but that adds the overhead of checking for null pointers.