Andrzej posted another thread about creating a new compact_optional class.
The goal of this class is to make it easy to create a new optional type
with a special sentinel value. This allows easy customization per instance
of an optional value.
This post is about the opposite approach: making it easier to specialize
optional to provide special behavior for all instances of a given optional
type.
When I bring this up, people often compare it to the vector<bool>
specialization, but this is a completely different issue. The problem of
vector<bool> is that it does not actually store a bool anywhere, so it
cannot return a reference to one. This would still store a T somewhere.
For the rest of the discussion, I will be assuming a typical standard
library implementation on a 64-bit system. Whenever I talk about specific
sizes of objects, mentally insert "on most common systems". The principles
apply generally.
Consider the case of optionalstd::string. Using the naive implementation,
this object is 8 bytes larger than std::string, due to alignment
requirements. However, there are certain representations of std::string
that are not actually possible. For instance, for common implementations of
std::string you cannot have std::string::size() ==
std::numeric_limitsstd::string::size_type::max() *. We can take advantage
of this if our std::string implementation stores the capacity as an integer
value, rather than a pointer to the end of the storage.
Therefore, we could have implementations that look something like this:
class string {
friend optional<string>;
};
template<>
class optionalstd::string {
optional(none_t) {
// Use friendship to just set capacity to sentinel value
}
explicit operator bool() const {
return m_data.capacity() ==
std::numeric_limitsstd::string::size_type::max();
}
};
optionalstd::string still stores std::string in it, so operator*() works
just fine. The initialized check is still just comparing a field against a
compile-time constant. This makes this purely a space optimization that
cannot be detected by the user, unless they use
sizeof(optionalstd::string).
I believe all of this is possible today with the current specification of
boost::optional (and would definitely be allowed for the proposed
std::experimental::optional). However, this requires the creator of each
specialization to implement the full optional interface. What's more, it is
especially tricky to specialize optional for a class template when you only
want to specialize on certain specializations of the class template.
My use case for this is my bounded::integer library. This library lets you
specify the bounds of your integer as template parameters. If your
bounded::integer has a narrower range than its underlying integer, you can
take advantage of this to make a more space-efficient optional. If,
however, the bounded::integer min and max are equal to that of the
underlying type, there is no extra space and we want the default optional
implementation. With the current specialization approach, it is the
responsibility of the specializer to reimplement all of optional.
A better approach to easily support this space optimization is as follows:
optional contains an instance of optional_storage