8

I am experimenting with implementing boost::optional like data structure using c++11 features. Here is what I have so far :

template<typename T>
struct maybe {
  bool valid;

  union {
    T value;
  };

  maybe() : valid(false) {}
  maybe(const T& _v) {
  valid = true;
    new (&value) T(_v);
  }
  maybe(const maybe& other) {
    if (other.valid) {
      valid = true;
      new (&value) T(other.value);
    }
    else valid = false;
  }

  ~maybe() {
     if (valid)
       value.~T();
  }

  bool is_valid() { return valid; }

  operator T&() {
    if (valid) return value;
    throw std::bad_exception();
  }
};

I make use of the unrestricted union feature to create a properly aligned space for the optional value that can be stored in-situ, instead of dynamically allocation space. Things work mostly, except when I want to create a maybe<> with a reference. For instance maybe<int&> causes g++ 4.7 to complain :

error: ‘maybe<int&>::<anonymous union>::value’ may not have reference type ‘int&’
because it is a member of a union

What should I do to make the maybe class store references? Any other improvements/suggestions to the class are also welcome.

keveman
  • 8,427
  • 1
  • 38
  • 46

2 Answers2

8

To make this work with references you definitely need an explicit specialization, because you can't do placement new of a reference: you need to use pointers for storage.

Beyond that, the code is missing a copy assignment operator. A move constructor, and move assignment operator would also be nice (especially since that's the #1 reason to reimplement boost::optional: the one in boost is lacking them).

R. Martinho Fernandes
  • 228,013
  • 71
  • 433
  • 510
  • "That makes your class not working with non-default constructible types" This is not true, though. The standard seems to indicate that automatic initialization and destruction are suppressed for members of unions. – keveman Aug 09 '12 at 00:40
  • 1
    BTW, `alignas(T) char data[sizeof(T)]` seems more C++11-ish than what you have. – keveman Aug 09 '12 at 00:43
  • 1
    @keveman it's probably how `aligned_storage` is implemented. I find `aligned_storage` a lot more explicit about its business. – R. Martinho Fernandes Aug 09 '12 at 00:47
  • @rmartinho To each, his own. The committee painstakingly moved the aligned_storage idiom to the language proper, so I don't feel like using a library wrapper over the language feature. – keveman Aug 09 '12 at 00:51
  • @keveman regarding the default constructibility, that's quite interesting. Could you please refer me to the appropriate standard sections? – R. Martinho Fernandes Aug 09 '12 at 00:58
  • Btw, I didn't notice before, but the copy constructor reads `valid` before it is initialised. It looks a lot like a copy assignment operator instead. Was that a transcription error or something? – R. Martinho Fernandes Aug 09 '12 at 01:01
  • [n2544.pdf](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2544.pdf) - 3rd page 3rd paragraph. – keveman Aug 09 '12 at 01:02
  • @rmartinho It was a bug in the copy constructor. Edited it out. – keveman Aug 09 '12 at 01:06
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/15102/discussion-between-keveman-and-r-martinho-fernandes) – keveman Aug 09 '12 at 01:09
4

The optional types were proposed for C++14 but due to some corner cases around behavior undefined in standards it got postponed to C++17.

Fortunately, the UB issue shouldn't matter to most people because all major compilers do define it correctly. So unless you are using old compilers, you can actually just drop in the code available to implement optional type in your project (it's just one header file):

https://raw.githubusercontent.com/akrzemi1/Optional/master/optional.hpp

Then you can use it like this:

#if (defined __cplusplus) && (__cplusplus >= 201700L)
#include <optional>
#else
#include "optional.hpp"
#endif

#include <iostream>

#if (defined __cplusplus) && (__cplusplus >= 201700L)
using std::optional;
#else
using std::experimental::optional;
#endif

int main()
{
    optional<int> o1,      // empty
                  o2 = 1,  // init from rvalue
                  o3 = o2; // copy-constructor

    if (!o1) {
        cout << "o1 has no value";
    } 

    std::cout << *o2 << ' ' << *o3 << ' ' << *o4 << '\n';
}
Shital Shah
  • 63,284
  • 17
  • 238
  • 185