13

Consider the following code:

struct s
{
    const int id;

    s(int _id):
        id(_id)
    {}
};
// ...
vector<s> v;  v.push_back(s(1));

I get a compiler error that 'const int id' cannot use default assignment operator.

Q1. Why does push_back() need an assignment operator?
A1. Because the current c++ standard says so.

Q2. What should I do?

  • I don't want to give up the const specifier
  • I want the data to be copied

A2. I will use smart pointers.

Q3. I came up with a "solution", which seems rather insane:

s& operator =(const s& m)
{
    if(this == &m) return *this;
    this->~s();
    return *new(this) s(m);
}

Should I avoid this, and why (if so)? Is it safe to use placement new if the object is on the stack?

curiousguy
  • 8,038
  • 2
  • 40
  • 58
Dave
  • 690
  • 4
  • 7
  • 16
  • 1
    About #1, it's a dynamic array. It's going to have to copy (or move) the elements. – chris Jul 22 '12 at 16:34
  • And because copying does `id = old.id` it cannot be const. – nullpotent Jul 22 '12 at 16:36
  • 1
    &chris That's the part I don't get, it should copy and not assign – Dave Jul 22 '12 at 16:36
  • 4
    You could enable C++11 and the code will compile. – kennytm Jul 22 '12 at 16:36
  • @KennyTM, Oh, you can move `const`s? And here I was getting a working sample of a `const int *` as the member working with it :/ It did work, though :p – chris Jul 22 '12 at 16:42
  • You can copy from `const` objects but you can't [copy/move] assign to a `const` object, even in C++11. – CB Bailey Jul 22 '12 at 16:56
  • 1
    For Q3: you must absolutely check for self-assignment, and then say `this->~s();` before the `new` call (as it is UB to overwrite the memory of an object whose destructor is non-trivial)... but I'm not sure if you're allowed to destroy an object inside its own member function. – Kerrek SB Jul 22 '12 at 17:01
  • 1
    If you want your data member to be copied during copy assignment it doesn't make any sense to declare it `const`. Why do you want it to be `const` or why don't you want to give up the `const`? – CB Bailey Jul 22 '12 at 17:14
  • [Q3 answered](https://stackoverflow.com/a/47475556/5470596) – YSC Nov 24 '17 at 15:19
  • @KerrekSB "_as it is UB to overwrite the memory of an object whose destructor is non-trivial_" why? – curiousguy Jul 07 '18 at 00:53
  • 1
    @curiousguy: I phrased that badly. It's not always UB; it's UB if the program depends on the side effects of the destructor (by [basic.life]). – Kerrek SB Jul 07 '18 at 21:03
  • What does *new(this) s(m) mean? – Robert Page Jan 07 '21 at 14:56

7 Answers7

5

C++03 requires that elements stored in containers be CopyConstructible and Assignable (see §23.1). So implementations can decide to use copy construction and assignment as they see fit. These constraints are relaxed in C++11. Explicitly, the push_back operation requirement is that the type be CopyInsertable into the vector (see §23.2.3 Sequence Containers)

Furthermore, C++11 containers can use move semantics in insertion operations and do on.

juanchopanza
  • 223,364
  • 34
  • 402
  • 480
4

I don't want to give up the const specifier

Well, you have no choice.

s& operator =(const s& m) {
    return *new(this) s(m); 
}

Undefined behaviour.

There's a reason why pretty much nobody uses const member variables, and it's because of this. There's nothing you can do about it. const member variables simply cannot be used in types you want to be assignable. Those types are immutable, and that's it, and your implementation of vector requires mutability.

Puppy
  • 144,682
  • 38
  • 256
  • 465
  • 3
    Why is this undefined behavior? – razeh Mar 06 '14 at 23:06
  • 1
    related question: https://stackoverflow.com/questions/47473621/placement-new-and-assignment-of-class-with-const-member why is it UB? – YSC Nov 24 '17 at 14:27
  • And it may not be. – YSC Nov 24 '17 at 15:26
  • It is undefined behavior, as the compiler assumes, that `const` members are constant - even during an assignment to the whole object. So after the placement new trick, the compiler might still use the old value of that `const` field, if it had been loaded into a register, for example. So UB. `std::launder()` is a way around it, but I do not recommend it. Make that `const` field `private` instead and use a getter function to allow its value to be read. – Kai Petzke Apr 10 '21 at 15:27
  • @KaiPetzke This was true up until c++ when basic.life replacement removed the restriction against const subobjects. See http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p1971r0.html#RU007 – doug Apr 09 '22 at 01:09
4
s& operator =(const s& m)
{
    if(this == &m) return *this;
    this->~s();
    return *new(this) s(m);
}

Should I avoid this, and why (if so)? Is it safe to use placement new if the object is on the stack?

You should avoid it if you can, not because it is ill-formed, but because it is quite hard for a reader to understand your goal and trust in this code. As a programmer, you should aim to reduce the number of WTF/line of code you write.

But, it is legal. According to

[new.delete.placement]/3

void* operator new(std::size_t size, void* ptr) noexcept;

3 Remarks: Intentionally performs no other action.

Invoking the placement new does not allocate or deallocate memory, and is equivalent to manually call the copy constructor of s, which according to [basic.life]/8 is legal if s has a trivial destructor.

YSC
  • 38,212
  • 9
  • 96
  • 149
  • For an indepth case study, see [`[basic.life]/8`](https://timsong-cpp.github.io/cppwp/basic.life#8) ans [this answer about it](https://stackoverflow.com/a/47475941/5470596). – YSC Nov 24 '17 at 15:16
3

Ok,

You should always think about a problem with simple steps.

std::vector<typename T>::push_back(args);   

needs to reserve space in the vector data then assigns(or copy, or move) the value of the parameter to memory of the vector.data()[idx] at that position.

to understand why you cannot use your structure in the member function std::vector::push_back , try this:

std::vector<const int> v; // the compiler will hate you here, 
                          // because this is considered ill formed.

The reason why is ill formed, is that the member functions of the class std::vector could call the assignment operator of its template argument, but in this case it's a constant type parameter "const int" which means it doesn't have an assignment operator ( it's none sense to assign to a const variable!!). the same behavior is observed with a class type that has a const data member. Because the compiler will delete the default assignment operator, expel

struct S
{
    const int _id; // automatically the default assignment operator is 
                   // delete i.e.  S& operator-(const S&) = delete;
};
// ... that's why you cannot do this
std::vector<S> v; 
v.Push_back(S(1234));

But if you want to keep the intent and express it in a well formed code this is how you should do it:

class s
{
    int _id;
public:
    explicit s(const int& id) :
    _id(id)
    {};

    const int& get() const
    {
    return _id; 
    }; // no user can modify the member variable after it's initialization

};
// this is called data encapsulation, basic technique!
// ...
std::vector<S> v ; 
v.push_back(S(1234)); // in place construction

If you want to break the rules and impose an assignable constant class type, then do what the guys suggested above.

organicoman
  • 154
  • 6
1

Q2. What should I do?

Store pointers, preferably smart.

vector<unique_ptr<s>> v;
v.emplace_back(new s(1));
Benjamin Lindley
  • 101,917
  • 9
  • 204
  • 274
0

It's not really a solution, but a workaround:

#include <vector>
struct s
{
  const int id;
  s(int _id):
    id(_id)
    {}
};

int main(){
  std::vector<s*> v;  
  v.push_back(new s(1));
  return 0;
}

This will store pointers of s instead of the object itself. At least it compiles... ;)

edit: you can enhance this with smart c++11 pointers. See Benjamin Lindley's answer.

steffen
  • 8,572
  • 11
  • 52
  • 90
-3

Use a const_cast in the assignment operator:

S& operator=(const S& rhs)
{
    if(this==&rhs) return *this;
    int *pid=const_cast<int*>(&this->id);
    *pid=rhs.id;
    return *this;
}
k73
  • 43
  • 5
  • 2
    I’m not 100% sure, but I’m pretty much afraid this may invoke UB –  Feb 03 '16 at 17:08