5

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?

  • 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. – François Andrieux Mar 04 '20 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. – François Andrieux Mar 04 '20 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. – noobius Mar 04 '20 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`. – Pete Becker Mar 05 '20 at 15:04

3 Answers3

6

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.

JohnFilleau
  • 4,045
  • 1
  • 15
  • 22
2

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.

doug
  • 3,840
  • 1
  • 14
  • 18
  • That is an interesting way to tell the compiler to ignore that you said `const`. – Mark Roberts Aug 08 '23 at 09:35
  • @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. – doug Aug 08 '23 at 17:17
0

Define your const as static const and that should take care of it.

Sohrab
  • 181
  • 2
  • 4