3

Suppose I have a class with some constant member:

class MyClass {
public:
   MyClass(int a) : a(a) {
   }
   MyClass() : MyClass(0) {
   }
   ~MyClass() {
   }
   const int a;
};

Now I want to store an instance of MyClass somewhere, e.g. as a global variable or as an attribute of another object.

MyClass var;

Later, I want to assign a value to var:

var = MyClass(5);

Obviously, this does not work, because the assign operator is invoked, which does not exist by default, because MyClass has a const attribute. So far so good.

My question is, how can I assign a value to var anyway? After all, var itself is not declared to be a constant.

My thoughts so far

I know that the problem does not exist if I use a pointer for var:

MyClass *var;
var = new MyClass(5);

However, I would not like to use a pointer for convenience reasons.

A potential solution is to overwrite the memory with placement new:

template<class T, class... Args>
T &emplaceVar(T &myVar, Args&&... args) {
   myVar.~T(); // free internal memory
   return *new (&myVar) T(args...);
}

emplaceVar(var, 5);

This would solve the problem, but I am not sure if this may cause memory leaks or any other issues I have not thought of due to my lack of experience in c++. Furthermore, I would have thought there must be an easier way. Is there?

Samufi
  • 2,465
  • 3
  • 19
  • 43
  • 2
    "After all, var itself is not declared to be a constant." but you decalred that its member is `const` in any object, whether the instance is `const` or not. – 463035818_is_not_an_ai Jun 17 '21 at 11:10
  • is it an option to make the member `private` ? – 463035818_is_not_an_ai Jun 17 '21 at 11:11
  • 1
    Overwriting a `const` subobject with placement-new causes undefined behaviour , basically – M.M Jun 17 '21 at 11:12
  • 1
    You can write an overloaded assignment operator for `var` -- of course this will not be able to modify `a` – M.M Jun 17 '21 at 11:12
  • @463035818_is_not_a_number The member must be public, but of course I could write a getter function for it (and not write a setter). But I would like to avoid writing trivial getters, if possible. – Samufi Jun 17 '21 at 11:13
  • It appears you `a` to be `const` and to also be `mutable`. You can achieve that my removing `const`. – Eljay Jun 17 '21 at 11:15
  • @M.M This is helpful to know. I always thought that `const` is just for additional checks by the compiler. Can you give some technical background on why overwriting const members with placement new would be dangerous / not work? – Samufi Jun 17 '21 at 11:16
  • well ok, then this doesnt make an answer, but nevertheless it is worth to consider: Instead of finding a way to modify the `const` member, a solution is to prevent modification of a non-const member. Make it `private` and provide no means to modify it from outside the class. – 463035818_is_not_an_ai Jun 17 '21 at 11:16
  • you can follow this answer: https://stackoverflow.com/a/45996145/1863938, but my advice is don't, because it adds an extra data member and complicates things. Idiomatic C++ code uses methods to access read-only properties (e.g. `std::vector::size()`), which can be stored in private non-const data members. – parktomatomi Jun 17 '21 at 11:18
  • @Eljay I do not fully understand your comment. `a` should not be mutable! The reason for that is not visible in my minimal example, of course. In the real code it is a shape attribute for a constant-sized container. – Samufi Jun 17 '21 at 11:19
  • 1
    @Samufi because when something is `const` the compiler assumes the value does not change. I suggest to read about `std::launder`. I don't understand it myself completely, but many examples for it are about placement new into something that is `const` – 463035818_is_not_an_ai Jun 17 '21 at 11:19
  • The normal solution would be to just not have it `const`, but don't modify it . Having `const` members of objects introduces a bunch of problems relating to being able to assign and move the object. You could hide it inside a class with non-const data member and public read-only accessor if you really wanted to . – M.M Jun 17 '21 at 11:22
  • 2
    You want to do `var = MyClass(5);`, which modifies `a`, hence you want `a` to be mutable. You could have a private `int actual_a;` and have a public `int const& a;` which refers to `actual_a`. – Eljay Jun 17 '21 at 11:22
  • i am hesitant to post it as answer, because it would be merely "stating the obvious" and you already said you want it public, but consider that `class MyClass { int a; };` meets your requirements: It can be copied and `a` cannot be modified, and it is the much simplar alternative, only downside is that you need a getter. If you are worried about writing `myObject.a();` instead of `myObject.a;`, then there are ways to achieve the latter even with the member being private, its a little hacky but much less then your current route – 463035818_is_not_an_ai Jun 17 '21 at 11:26
  • can you clarify why the member must be public? – 463035818_is_not_an_ai Jun 17 '21 at 11:29
  • @Eljay and @463035818_is_not_a_number This sounds like the way to go. In fact, the reason for the `constant` declaration is that I do not want the object to be modified from outside. At the same time, I want to avoid writing `myObject.a()` for consistency with other attribute access (existing code). If you write an answer explaining why the placement new does not work (and neither an other solution) and point to this alternative, I will happily accept it. This was the missing bit of information I needed, and I want it to be accessible for other users with similar problems. – Samufi Jun 17 '21 at 11:30
  • @463035818_is_not_a_number I only need public read-only access to the value of the member. – Samufi Jun 17 '21 at 11:32
  • Since C++ does not have *properties*, to get public read-only access to the value of the member the C++ way is to have a public *getter* to access it, but no public *setter* to modify it. – Eljay Jun 17 '21 at 11:36

2 Answers2

3

const members are problematic in general for the very reason you discovered.

The much simpler alternative is to make the member private and take care to provide no means to modify it from outside the class:

class MyClass {
public:
   MyClass(int a) : a(a) {
   }
   MyClass() : MyClass(0) {
   }
   ~MyClass() {
   }
private:
   int a;
};

I did not add a getter yet, because you say access via myObject.a is a hard requirement. Enabling this requires a bit of boilerplate, but it is much less hacky than modifiying something that must not be modified:

class MyClass {
public:
   struct property {
       const int& value;
       operator int(){ return value;}
       property(const property&) = delete;
   };

   MyClass(int a = 0) : value(a) {}
private:
   int value;
public:
    property a{value};
};

int main(){
    MyClass myObject{5};
    int x = myObject.a;
    //myObject.a = 42; // error
    //auto y = myObject.a; // unexpected type :/
}

Live Demo

Drawback is that it does not play well with auto. If by any means you can accept myObject.a() I would suggest to use that and keep it simple.

463035818_is_not_an_ai
  • 109,796
  • 11
  • 89
  • 185
  • The treatment of `auto` isn't so bad, `y` is still a read-only `int` – Caleth Jun 17 '21 at 11:55
  • @Caleth actually `auto` isnt the issue, but rather wrong expectations when deducing the type, imange a `foo< decltype(x)>`. And one problem I forgot to mention is that it is too easy to get a dangling reference – 463035818_is_not_an_ai Jun 17 '21 at 12:00
  • 1
    You should probably at least delete copy constructor. – Jarod42 Jun 17 '21 at 13:09
  • 1
    It is possible to have a `private` member named (say) `actual_a`, and specify `a` as being a `const` reference to `actual_a`. All constructors of `MyClass` will need to properly initialise `a` so it refers to the `actual_a` member (in their initialiser list) but programmer-defined assignment operators can reassign `actual_a` without needing (or being allowed) to change what `a` refers to. – Peter Jun 17 '21 at 13:13
  • @Peter but that would still prevent assignments, no? ... oh well, I think I get it. Maybe I'll update the answer when I find the time – 463035818_is_not_an_ai Jun 17 '21 at 13:54
1

how can I assign a value to var anyway?

You can do that with a user-defined assignment operator:

class MyClass {
public:
   MyClass &operator=(const MyClass &o)
   {
         // Implement your assignment here

         return *this;
   }

   // ...
};

Your assignment operator can do anything that any operator= overload can. The only thing it can't do is assign anything to its const class member. That's because it's constant.

If a class does not have user-defined assignment operator, the default assignment operator assigns each member of the assigned-to object from the same member of the assigned-from object. However the default assignment operator is deleted from any class that has a const member, because that, of course, is no longer possible.

In your user-defined operator you can do whatever it means to assign one of these objects from another one. The only thing it can't do is the same thing any other class method can't do: modify a const class member.

You mentioned manual invocation of a destructor and placement new. That's possible, provided that all requisite requirements are met and undefined behavior is carefully avoided. However, technically, it wouldn't be assignment, but rather a manual destruction and construction of another object.

Sam Varshavchik
  • 114,536
  • 5
  • 94
  • 148
  • 1
    As of c++20 you can modify a const member object. There's even a constexpr function to do so, `construct_at` and well as `destroy_at` in case the the object has a non-trivial dtor. – doug Apr 14 '22 at 04:31