15

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?

user541686
  • 205,094
  • 128
  • 528
  • 886
  • This appears to compile and link with VC 2010. Can you provide more information about your environment? Is this a complete test case that reproduces that compile failure? – DRH Dec 11 '11 at 22:24
  • @DRH: I'm on VC 2008, sorry. And yes, that's the entire test case. – user541686 Dec 11 '11 at 22:26
  • While for other operations you would need the assignment operator, I cannot think of any possible reasons why `push_back` would have that requirement... then again, it might be that the implementation checks the `Assignable` requirement. – David Rodríguez - dribeas Dec 11 '11 at 22:45

4 Answers4

10

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:

  1. Make name non-const.
  2. Write your own copy-assignment operator, and think of a way to deal with "copying" a const member.
Oliver Charlesworth
  • 267,707
  • 33
  • 569
  • 680
10

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.

Community
  • 1
  • 1
CB Bailey
  • 755,051
  • 104
  • 632
  • 656
  • 1
    Oh huh... so it needs to be assignable even if I never assign anything? Didn't know that. – user541686 Dec 11 '11 at 22:20
  • @Philipp: Actually, the vector doesn't assign anything when reallocating. It typically only copy- or move-constructs the new range and then deletes the old one. – Kerrek SB Dec 11 '11 at 22:24
  • Hmm... Why does `vector` perform copy assignments though? Can't it do copy construction + destruction, instead of using `operator =`? – user541686 Dec 11 '11 at 22:27
  • @Mehrdad: Many of the operations on `std::vector` require assignment (consider `insert`, for instance). – Oliver Charlesworth Dec 11 '11 at 22:32
  • 1
    @OliCharlesworth: Why does `insert` need assignment, though? I would think it needs a copy constructor (with `allocator::allocate`) instead, because it's not assigning to a valid object, but to a newly allocated, unformatted chunk of memory, right? – user541686 Dec 11 '11 at 22:34
  • 1
    @Mehrdad: Because in pseudocode, it's going to be doing something like: `for (int i = size(); i > pos; i--) { elements[i] = elements[i-1]; }`. – Oliver Charlesworth Dec 11 '11 at 22:35
  • @Mehrdad: It _could_ 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. – CB Bailey Dec 11 '11 at 22:47
  • @CharlesBailey: Ohhhhhh I see the logic behind it now. Thanks a lot! – user541686 Dec 11 '11 at 22:50
7

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.

Dima
  • 38,860
  • 14
  • 75
  • 115
1

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.

Philipp
  • 48,066
  • 12
  • 84
  • 109