6

I'm designing a class that ought to have a const data member called K. I also want this class to have a copy assignment operator, but the compiler seems to implicitly delete the copy assignment operator from any class with const data members. This code illustrates the essential problem:

class A
{
    private:
       const int K;
    public:
       A(int k) : K(k) {} // constructor
       A() = delete; // delete default constructor, since we have to set K at initialization

       A & operator=(A const & in) { K = in.K; } // copy assignment operator that generates the error below
}

Here's the error it generates:

constructor.cpp:13:35: error: cannot assign to non-static data member 'K' with const- 
qualified type 'const int'
            A & operator=(A const & in) { K = in.K; }
                                          ~ ^
constructor.cpp:6:13: note: non-static data member 'K' declared const here
            const int K;
            ~~~~~~~~~~^
1 error generated.

I think I understand why the compiler does this; the instance of the class I'd want to copy to has to exist before it can be copied to, and I can't assign to K in that target instance if it's const, as I'm trying to do above.

Is my understanding of this problem correct? And if so, is there a way around this problem? That is, can I define a copy constructor for my class and still give K const-like protection?

4
  • 5
    It's generally a mistake for a class to have a const data member. It's usually better to just guarantee it's constness by making it private and not providing a setter. It's weaker than const but the trade off usually tilts in favor of not using const here. Commented Mar 4, 2020 at 21:43
  • 5
    "can I define a copy constructor for my class" A copy constructor is easy to implement, but an assignment operator is not. It's up to you to decide what it means to assign to a A without being able to change k. You will probably find that it doesn't make sense, meaning that it doesn't make sense to try to implement the operator. Commented Mar 4, 2020 at 21:46
  • 2
    If you think about it, an object is constructed and its const variable is initialize at creation for the very first time that's all fine and good. Now comes time to assign it to another object, at this point you can't reassign the const member data to another value, and violate the definition of const. Commented Mar 4, 2020 at 21:57
  • 'K' should either be const or not const. There is no middle ground. If you need to be able to modify it, it should not be marked const. Commented Mar 5, 2020 at 15:04

2 Answers 2

9

In C++, a class with a const data member may have a copy constructor.

#include <iostream>

class A
{
private:
    const int k_;
public:
    A(int k) : k_(k) {}
    A() = delete;
    A(const A& other) : k_(other.k_) {}

    int get_k() const { return k_; }
};

int main(int argc, char** argv)
{
    A a1(5);
    A a2(a1);

    std::cout << "a1.k_ = " << a1.get_k() << "\n";
    std::cout << "a2.k_ = " << a2.get_k() << "\n";
}

Output:

a1.k_ = 5
a2.k_ = 5

In C++, a class with a const data member may not use the default assignment operator.

class A
{
private:
    const int k_;
public:
    A(int k) : k_(k) {}
    A() = delete;
    A(const A& other) : k_(other.k_) {}

    int get_k() const { return k_; }
};

int main(int argc, char** argv)
{
    A a1(5);
    A a2(0);

    a2 = a1;
}

Yields a compile time error:

const_copy_constructor.cpp: In function ‘int main(int, char**)’:
const_copy_constructor.cpp:18:10: error: use of deleted function ‘A& A::operator=(const A&)’
   18 |     a2 = a1;
      |          ^~
const_copy_constructor.cpp:1:7: note: ‘A& A::operator=(const A&)’ is implicitly deleted because the default definition would be ill-formed:
    1 | class A
      |       ^
const_copy_constructor.cpp:1:7: error: non-static const member ‘const int A::k_’, can’t use default assignment operator

In C++, a class with a const data member may use a non-default assignment operator as long as you don't attempt to change the const data member, but you better think long and hard about what it means to use this assignment operator if one of the underlying members cannot be modified.

class A
{
private:
    const int k_;
public:
    A(int k) : k_(k) {}
    A() = delete;
    A(const A& other) : k_(other.k_) {}

    A& operator=(A const& other)
    {
        // do nothing
        return *this;
    }

    int get_k() const { return k_; }
};

int main(int argc, char** argv)
{
    A a1(5);
    A a2(0);

    a2 = a1;
}

Yields no compile time errors.

Sign up to request clarification or add additional context in comments.

Comments

9

As of c++20, you can now copy objects that have one or more const member objects by defining your own copy-assignment operator.

class A
{
private:
    const int K;
public:
    A(int k) : K(k) {} // constructor
    A() = delete; // delete default constructor, since we have to set K at initialization
    // valid copy assignment operator in >= c++20
    A& operator=(A const& in) {
        if (this != &in)
        {
            std::destroy_at(this);
            std::construct_at(this, in);
        }
        return *this;
    }
};

This was made possible by changes in basic.life which allows transparent replacement of objects, including those containing const sub-objects, w/o UB.

5 Comments

That is an interesting way to tell the compiler to ignore that you said const.
@MarkRoberts It is but const has been so rigid that standard guidelines are to put class member objects under private and provide public getters that return a constant ref. And that's even easier to ignore with a simple const_cast. This change allows one to actually have constant objects in an aggregate struct and treat vectors of them normally like being able to sort and copy them while maintaining invariants. Not possible previously w/o losing aggegateness.
Should a class containing a const member be assignable?
@MatG Sure. Why not. One is assigning the whole object. The object's invariants are maintained but the old one is replaced by a new one. You can't, of course, assign to an object that is, itself, const.
@MatG It depends. If your design depends on being able to keep pointers (including shared_ptr, etc.) or references to immutable objects, so that you pass pointers/refs around and/or copy them instead of copying objects, then you don't want the object contents to EVER change, even by reassignment. If you are instead only concerned with an object preserving its own invariants, then you might want to allow existing objects to be overwritten with new ones via reassignment, move, etc. Either way, you need to be very clear about your design intent so that some later programmer doesn't violate it.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.