Is it allowed to assign from an object that has some indeterminate values?

Viewed 54

Consider the following class, which can either be in a "non-empty" or "empty" state, and in the "empty" state the other member is default initialized (hence has an indeterminate value):

struct MaybeInt {
  bool has_value;
  int value;

  MaybeInt()      : has_value(false) {}
  MaybeInt(int v) : has_value(true ), value(v) {}
};

Is it allowed to assign from a default-constructed MaybeInt, as in:

MaybeInt empty, another;
another = empty; // OK?

How about construction?

MaybeInt empty, another(empty); // OK?

Does the answer change if MaybeInt::value has type char?

2 Answers

another = empty is indeed UB because empty.value has an indeterminate value and because the implicitly defined copy constructor of a class copies all members.

The trick is to place the member value in a union. This way the implicitly defined copy constructor of the union copies the object representation :

struct MaybeInt {
  bool has_value;
  union {
    int value;
    char _dumb;
    };

  MaybeInt()      : has_value(false) {}
  MaybeInt(int v) : has_value(true ), value(v) {}
  };

NB: This is a low level trick to have an optional that is trivially copyable. This is realy important for code execution speed. This class can be passed through function call on a cpu register, while it would be impossible if it were not trivially copyable.

Your code results in undefined behaviour.

MaybeInt empty, another;
another = empty; 

OK. another has the same undefined value that empty had. Technically working, leading to bugs in practise.

MaybeInt empty, another(empty);

empty contains an undefined value, and this is copied to another which has the same value, only now your class thinks it is defined.

Use std::optional, everything which should be optional is solved in a standard way. (C++ 17)

optional<int> x; // empty or not, you can use `value_or()` to get a default value.
Related