0

I wrote a custom optional class (since I am forced to use C++98 without STL). It looks like this:

template <typename T>
struct optional {
    char value[sizeof(T)];
    bool has_value;

    T& operator*() {
        return *reinterpret_cast<T*>(value);
    }
};

The compiler produces the warning dereferencing type-punned pointer will break strict aliasing rules.

What can I do to make this class without UB? Maybe memcpy should be used, but I don't understand how.

Jouni
  • 321
  • 1
  • 8

2 Answers2

1

One way you can avoid the warning is by having a type-discriminating uinion. Unfortunately, in C++98 it would only work for types which do have default constructors.

I.e.:

...
union {
   char empty;
   T value;
} storage;
bool has_value;
...

// when object is set to value
new (&storage.value) T(/* arg *);
has_value = true;

...
T& operator() {
   // check for has_value should be here
   return storage.value;
...

~optiona() {
   if (has_value) storage.value.~T();
...

This works, because when union is created, the first element is constructed (empty char). When you put value into the optional, you activate a second member of the union (value), and it is well-defined to access it since.

SergeyA
  • 61,605
  • 5
  • 78
  • 137
1

What can I do to make this class without UB?

  • Use placement-new to create the object.
  • Call the destructor of the created object in destructor of optional
  • Do not reinterpret the address of the storage. Use the pointer returned from placement new. This unfortunately means that you need to store the pointer as a member. You can replace the bool since null pointer would signify empty state.
  • Take care of alignment. This can be quite tricky pre-C++11. You may need to rely on non-standard language features to achieve this. Given a pointer member which has quite strict alignment, and the fact that C++98 has no overaligned types, you might get away with ignoring alignment for most types.

It would be much easier to allocate the object dynamically. Slower, very likely. But simpler and standard conformant.

eerorika
  • 232,697
  • 12
  • 197
  • 326
  • that's true but I wanted to know what to to with reinterpret_cast. One problem at a time. – Jouni Jun 11 '20 at 15:10
  • 3
    @Jouny Actually, you would have to store the pointer returned by placement new. You cannot use reinterpret_cast in standard conforming way here. – eerorika Jun 11 '20 at 15:11
  • @Jouny - Simple. Don't do reinterpret cast. Instead of the `bool`, store a pointer. Initialize it to a null pointer to signal a vacancy, and set it to the result of placement new. – StoryTeller - Unslander Monica Jun 11 '20 at 15:12
  • @StoryTeller-UnslanderMonica the problem with using a typed pointer is that it will introduce latency through extra indirection. – SergeyA Jun 11 '20 at 15:20
  • @SergeyA There's not much else you can do if you want C++98 standard conformance through. The rules are strict. – eerorika Jun 11 '20 at 15:22
  • @eerorika this is indeed true. – SergeyA Jun 11 '20 at 15:28
  • @Jouny Like this: https://github.com/gcc-mirror/gcc/blob/master/libstdc%2B%2B-v3/include/std/optional This implementation uses union, like Sergey's answer. The use of non-trivial types as union member was made possible in one of the standards after C++98. – eerorika Jun 11 '20 at 15:56