HiveBrain v1.2.0
Get Started
← Back to all entries
patterncppMinor

Reinventing std::optional

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
reinventingstdoptional

Problem

I decided to make my own version of C++17's std::optional

It's basically a class that can optionally hold a variable (as the name suggests), it avoids having to resort to std::pair to check whether a variable is set.

I'd like some feedback regarding the overall implementation

template
class Optional {
 public:
  /**
   * The constructor
   */
  Optional() :
    is_set_(false) {}
  /**
   * The constructor
   * @param val The value to set it to
   */
  Optional(const T& val) :
    val_(val), is_set_(true) {}

  /**
   * Sets the value
   * @param val The value to set it to
   */
  void set(const T& val) { val_ = val; is_set_ = true; }

  /**
   * @return The value
   */
  T get() const { return val_; }

  /**
   * Clears the value
   */
  void clear() { val_ = T{}; is_set_ = false; }

  /**
   * @return Whether the value is set
   */
  bool empty() { return !is_set_; }

  /* Operators */

  Optional operator=(const T& val) {
    val_ = val;
    is_set_ = true;
    return *this;
  }

  operator T() const {
    return val_;
  }

  bool operator==(const bool& val) const {
    return (is_set_ == val);
  }

  explicit operator bool() const { return is_set_; }

 private:
  T val_{};
  bool is_set_ = false;
};

Solution

Prefer value type semantic

Try to make classes default constructible and equality comparable whenever this makes sense. Currently your Optional class doesn't fulfil neither. The following code will not compile

class A { int a; A() = delete; };

int main()
{
  Optional i;
}


Even though it might make sense to have an optional. Also consider this example

struct B { B() { /* insert something super long */ } }:

int main()
{
  Optional opt_b; // this line takes really long!
}


Here is an example of how to use std::aligned_storage to use uninitialised memory. I think this could help you as well.

Do not return by value if you can

The line

T get() const { return val_; }


Makes a copy of T while returning val_. Imagine val_ is a very big std::string. This could be very expensive in code which calls get() multiple times. If you want read-only access, prefer to use

const T& get() const& noexcept { return val_; }


instead.

Make use of move assignments

Currently you have to copy values into your optional. But what, if you just want to move something big that you want to return from a function? You
need to add constructors for rvalue-Ts.

Optional(T&& value)
 : val_{std::move(value)}
 , is_set_{true} {}

Code Snippets

class A { int a; A() = delete; };

int main()
{
  Optional<A> i;
}
struct B { B() { /* insert something super long */ } }:

int main()
{
  Optional<B> opt_b; // this line takes really long!
}
T get() const { return val_; }
const T& get() const& noexcept { return val_; }
Optional(T&& value)
 : val_{std::move(value)}
 , is_set_{true} {}

Context

StackExchange Code Review Q#158311, answer score: 6

Revisions (0)

No revisions yet.