Representing optional values in D
Asked Answered
F

1

6

I'm about to write a parser to read a text file line by line into structs of different types and giving these structs to a callback (observer or visitor - not sure yet).

The text file contains MT-940 data - a SWIFT bank statement.

These lines consist of a marker which specifies the type and some fields - e.g. a date - which should be parsed into type-safe members of my message. Some of these fields are optional - so my question is: How do I represent optional values in D.

C++ provides my things like boost::optional which you might know.

I currenty work around this by implementing an Optional(T) on my own (see code at the end of this post). It is a struct which contains a ValueHolder instance which might be null - which marks the case where no value has been assigned. I overwrote the copy-c'tor and the assignment operator to create a deep-copy of the ValueHolder if necessary.

Is this the way to go? Is there any other - more simple - option I just cannot see?

This is my code - not necessarily feature complete yet:

struct Optional(T)
{
  class ValueHolder
  {
    T value;

    this(T v)
    {
      value = v;
    }
  }

  private ValueHolder m_value;

  /* Construction without value / with value */

  this(T value)
  {
    m_value = new ValueHolder(value);
  }

  /* Copy construction / assignment */

  ref Optional!(T) opAssign(Optional!(T) rhs)
  out
  {
    if (rhs.m_value !is null)
    {
      assert(rhs.m_value != m_value);
    }
    else
    {
      assert(m_value is null);
    }
  }
  body
  {
    m_value = null;

    if (rhs)
    {
      m_value = new ValueHolder(rhs.m_value.value);
    }

    return this;
  }

  ref Optional!(T) opAssign(T value)
  out
  {
    assert(hasValue());
    assert(m_value.value == value);
  }
  body
  {
    if (m_value is null)
    {
      m_value = new ValueHolder(value);
    }
    else
    {
      m_value.value = value;
    }

    return this;
  }

  this(Optional!(T) rhs)
  out
  {
    if (rhs.m_value !is null)
    {
      assert(rhs.m_value != m_value);
    }
    else
    {
      assert(m_value is null);
    }
  }
  body
  {
    if (rhs.m_value !is null)
    {
      m_value = new ValueHolder(rhs.m_value.value);
    }
  }

  /* Implicit cast to bool */

  bool hasValue() const
  {
    return m_value !is null;
  }

  X opCast(X: bool)()
  {
    return hasValue();
  }

  /* Value access */

  T opUnary(string s)() const
  in
  {
    assert(s == "*");
    assert(m_value !is null);
  }
  body
  {
    return m_value.value;
  }
}

/* Default Constructed Struct does not have a value assigned */
unittest
{
  Optional!(int) x;
  assert(x.hasValue() == false);
  assert(!x);
}

/* Construction with value */
unittest
{
  Optional!(int) x = 3;

  assert(x);
  assert(x.hasValue());
}

/* Assignment operator does copy the value */
unittest
{
  Optional!(int) x = 3;
  Optional!(int) y;

  assert(x);
  assert(!y);

  y = x;
  assert(&x != &y);
  assert(x);
  assert(y);

  y = 12;
  assert(x.m_value.value != y.m_value.value);
  assert(*y == 12);

  Optional!(int) z;
  x = z;
  assert(!x);
  assert(!z);
  assert(y);
}
Farthingale answered 4/8, 2014 at 19:59 Comment(0)
B
10

For optional values, the D standard library provides the struct template Nullable in the module std.typecons.

Bousquet answered 4/8, 2014 at 21:36 Comment(4)
Now that I know it ... it's so obvious :) ThanksFarthingale
You should be careful when using Nullable, as it allows implicit conversion to the wrapped type, i.e., if you have a Nullable!int ni, the compiler will not stop you from doing int n = ni. If ni is "null", this will throw an exception at runtime.Instrumentalist
@Instrumentalist It doesn't throw an exception. It asserts. But yes, it will check whether the Nullable!T is "null" when you try and use it as a T.Floatplane
Thanks for the hint ... I expected something like thisFarthingale

© 2022 - 2024 — McMap. All rights reserved.