18

Consider the following:

class A {
public:
    const int c; // must not be modified!

    A(int _c)
    :   c(_c)
    {
        // Nothing here
    }

    A(const A& copy)
    : c(copy.c)
    {
        // Nothing here
    }    
};



int main(int argc, char *argv[])
{
    A foo(1337);

    vector<A> vec;
    vec.push_back(foo); // <-- compile error!
    
    return 0;
}

Obviously, the copy constructor is not enough. What am I missing?

EDIT:
Ofc. I cannot change this->c in operator=() method, so I don't see how operator=() would be used (although required by std::vector).

Gabriel Staples
  • 36,492
  • 15
  • 194
  • 265
eisbaw
  • 2,678
  • 2
  • 19
  • 18
  • 5
    *What* is the compile error? *Don't be vague, be an [ace](http://tinyurl.com/so-hints); write a [proper](http://sscce.org/) [test-case!](http://www.xs4all.nl/~weegen/eelis/iso-c++/testcase.xhtml)* –  Nov 08 '10 at 13:38
  • 2
    I think you have a choice: either you lose the const, or you lose the ability to use the vector. If you start working around it and allow `operator=` to modify the const member, you have now given means for any piece of code to do the same. – UncleBens Nov 08 '10 at 15:57
  • const int c; // must not be modified! Your comment above, does that mean 'c' should not modified by something that uses objects of class A or by the members of class A itself? – yasouser Nov 08 '10 at 17:33
  • @anand: c should only be set in the constructor. In my specific case, i have a pointer to some parent node. task * const parent; this pointer must not be re-seated, so changes are not allowed to c either in-class or externally. – eisbaw Nov 09 '10 at 06:54
  • 1
    @eisbaw: To me the choice of 'const int c' as a member of class A is the root cause of the problem. Hence my question. Having an operator=() and using const_cast<> to cast away const to assign a value to 'c' sounds like a hack to fix the compiler error and not a solution to the actual problem. – yasouser Nov 09 '10 at 16:21
  • @eisbaw You know, you could just make this a vector of smart pointers to your class. That would solve your problem without having to change any definitions. – Conspicuous Compiler Nov 09 '10 at 17:40
  • This code no longer produces any errors whatsoever as of C++11. I've noted this in my answer here: https://stackoverflow.com/a/63895415/4561887. – Gabriel Staples Sep 15 '20 at 05:07

10 Answers10

22

I'm not sure why nobody said it, but the correct answer is to drop the const, or store A*'s in the vector (using the appropriate smart pointer).

You can give your class terrible semantics by having "copy" invoke UB or doing nothing (and therefore not being a copy), but why all this trouble dancing around UB and bad code? What do you get by making that const? (Hint: Nothing.) Your problem is conceptual: If a class has a const member, the class is const. Objects that are const, fundamentally, cannot be assigned.

Just make it a non-const private, and expose its value immutably. To users, this is equivalent, const-wise. It allows the implicitly generated functions to work just fine.

GManNickG
  • 494,350
  • 52
  • 494
  • 543
  • Well said. I guess it was the comment after the `const int c` that made me assume that there was no other work-around for @eisbaw's situation. The old adage rings true: "why make things complicated for ourselves?" – Lee Netherton Nov 16 '10 at 12:50
  • Actually, it is not that easy to "just drop 'const'". Why? Because dropping 'const' at member fields effectively enforces you to drop 'const' in the constructor arguments, which in turn forces you to drop 'const' in the place where you use it etc... (I think, the idea should be clear). The solution with std::vector of pointers should work though (although it is not very elegant). By the way, in VS2010 you can push_back elements without assignment operator into the std::vector. – Dmitrii Semikin Jun 01 '12 at 13:07
  • 1
    @DmitriiSemikin Removing const from the field doesn't affect the parameters. If you're assigning by value, the value copies into the non-const field just fine. class C { int mX; ClassName(const int x) : mX(x) {} }; – srm Jun 19 '19 at 15:10
  • @GManNickG There is another point of view. After copying an object becomes a different object. Therefore, constness of members doesn't apply in object copying context. Unfortunately, now I have to choose between const members semantic and assigning to an object. – Oleksa Jul 09 '20 at 10:44
16

An STL container element must be copy-constructible and assignable1(which your class A isn't). You need to overload operator =.

1 : §23.1 says The type of objects stored in these components must meet the requirements of CopyConstructible types (20.1.3), and the additional requirements of Assignabletypes


EDIT :

Disclaimer: I am not sure whether the following piece of code is 100% safe. If it invokes UB or something please let me know.

A& operator=(const A& assign)
{
    *const_cast<int*> (&c)= assign.c;
    return *this;
}

EDIT 2

I think the above code snippet invokes Undefined Behaviour because trying to cast away the const-ness of a const qualified variable invokes UB.

Prasoon Saurav
  • 91,295
  • 49
  • 239
  • 345
  • But operator= changes the contents of *this; how would you change this->c ? – eisbaw Nov 08 '10 at 13:46
  • 4
    @eisbaw: Sorry but you cannot assign to `this->c`[because it is a constant] inside the overloaded `operator=`. – Prasoon Saurav Nov 08 '10 at 13:50
  • 1
    @eisbaw: Could you not write an empty operator= that simply ignores whatever you pass to it? That maintains the required interface but preserves the integrity of c. – JTeagle Nov 08 '10 at 13:58
  • @Prasson: That is why i posted this question in the first place! – eisbaw Nov 08 '10 at 14:00
  • 2
    @Prasoon: If *this was declared const (e.g. A const obj (3); obj = A(42);), then it is UB. Otherwise it's simply a [bad idea](http://chat.stackoverflow.com/transcript/message/87298#87298). –  Nov 08 '10 at 14:17
  • 2
    @Roger : Yes right. ` §7.1.​5.1/4` says `Except that any class member declared mutable (7.1.1) can be modified, any attempt to modify a const object during its lifetime (3.8) results in undefined behavior.` but we don't have any `const` object here so it should be fine though its a bad idea. – Prasoon Saurav Nov 08 '10 at 14:19
  • 1
    Actually your statement is not totally true. vector requires the element to be copy-constructible and assignable but not all containers enforce this (list does not enforce assignability). – CashCow Nov 08 '10 at 14:20
  • 1
    `c` is declared `const` within `class A` so the code is always UB right? – Mark B Nov 08 '10 at 14:43
  • @Mark B : Yes! Let me add that to the post. – Prasoon Saurav Nov 08 '10 at 14:44
  • Is this restriction lifted in C++0x ? `emplace_back` comes to mind as a possibility to insert a new element that shouldn't change (as long as there is enough space). – Matthieu M. Nov 08 '10 at 17:35
  • @Oli: Possibly, but only if you treat operator= as 'make an exact copy of'. If you treat it as 'copy all the relevant parts from', then it makes some sense, it's just that the list of 'relevant parts' here would be empty. If operator= always had to be 'make an exact copy of', then there would be no point in allowing it to be overridden - it could be a default every time because the compiler would know how to duplicate the object members exactly (it could do a memcpy()!). – JTeagle Nov 09 '10 at 07:26
  • It seems to me this becomes valid and well-defined behavior by the standard if you add `volatile` and make the class member variable `volatile const int c;` instead of just `const int c;`. I can't say this is a great idea, but I think what you are doing then becomes well-defined behavior and perfectly valid. Otherwise, the behavior is undefined only because _reads_ of `c` may be cached and/or optimized out since it is only `const`. See my answer here: https://stackoverflow.com/questions/4136156/const-member-and-assignment-operator-how-to-avoid-the-undefined-behavior/63896118#63896118. – Gabriel Staples Sep 15 '20 at 06:22
7

You're missing an assignment operator (or copy assignment operator), one of the big three.

sje397
  • 41,293
  • 8
  • 87
  • 103
2

The stored type must meet the CopyConstructible and Assignable requirements, which means that operator= is needed too.

icecrime
  • 74,451
  • 13
  • 99
  • 111
1

Probably the assignment operator. The compiler normally generates a default one for you, but that feature is disabled since your class has non-trivial copy semantics.

Frédéric Hamidi
  • 258,201
  • 41
  • 486
  • 479
0

I recently ran into the same situation and I used a std::set instead, because its mechanism for adding an element (insert) does not require the = operator (uses the < operator), unlike vector's mechanism (push_back).

If performance is a problem you may try unordered_set or something else similar.

0

I think the STL implementation of vector functions you are using require an assignment operator (refer Prasoon's quote from the Standard). However as per the quote below, since the assignment operator in your code is implicitly defined (since it is not defined explicitly), your program is ill-formed due to the fact that your class also has a const non static data member.

C++03

$12.8/12 - "An implicitly-declared copy assignment operator is implicitly defined when an object of its class type is assigned a value of its class type or a value of a class type derived from its class type. A program is illformed if the class for which a copy assignment operator is implicitly defined has:

— a nonstatic data member of const type, or

— a nonstatic data member of reference type, or

— a nonstatic data member of class type (or array thereof) with an inaccessible copy assignment operator, or

— a base class with an inaccessible copy assignment operator.

Chubsdad
  • 24,777
  • 4
  • 73
  • 129
0

You also need to implement a copy constructor, which will look like this:

class A {
public:
    const int c; // must not be modified!

    A(int _c)
    ...

    A(const A& copy)
    ...  

    A& operator=(const A& rhs)
    {
        int * p_writable_c = const_cast<int *>(&c);
        *p_writable_c = rhs.c;
        return *this;
    }

};

The special const_cast template takes a pointer type and casts it back to a writeable form, for occasions such as this.

It should be noted that const_cast is not always safe to use, see here.

Alexey
  • 1,198
  • 1
  • 15
  • 35
Lee Netherton
  • 21,347
  • 12
  • 68
  • 102
0

Workaround without const_cast.

A& operator=(const A& right) 
{ 
    if (this == &right) return *this; 
    this->~A();
    new (this) A(right);
    return *this; 
} 
van
  • 74,297
  • 13
  • 168
  • 171
Alexey Malistov
  • 26,407
  • 13
  • 68
  • 88
0

I just want to point out that as of C++11 and later, the original code in the question compiles just fine! No errors at all. However, vec.emplace_back() would be a better call, as it uses "placement new" internally and is therefore more efficient, copy-constructing the object right into the memory at the end of the vector rather than having an additional, intermediate copy.

cppreference states (emphasis added):

std::vector<T,Allocator>::emplace_back

Appends a new element to the end of the container. The element is constructed through std::allocator_traits::construct, which typically uses placement-new to construct the element in-place at the location provided by the container.

Here's a quick demo showing that both vec.push_back() and vec.emplace_back() work just fine now.

Run it here: https://onlinegdb.com/BkFkja6ED.

#include <cstdio>
#include <vector>

class A {
public:
    const int c; // must not be modified!

    A(int _c)
    :   c(_c)
    {
        // Nothing here
    }

    // Copy constructor 
    A(const A& copy)
    : c(copy.c)
    {
        // Nothing here
    }    
};

int main(int argc, char *argv[])
{
    A foo(1337);
    A foo2(999);

    std::vector<A> vec;
    vec.push_back(foo); // works!
    vec.emplace_back(foo2); // also works!
    
    for (size_t i = 0; i < vec.size(); i++)
    {
        printf("vec[%lu].c = %i\n", i, vec[i].c);
    }
    
    return 0;
}

Output:

vec[0].c = 1337
vec[1].c = 999
Gabriel Staples
  • 36,492
  • 15
  • 194
  • 265