v***@gmail.com
2015-06-24 16:55:49 UTC
Hi,
I'd like to bring up this topic again. I know Andrzej brought it up a
couple of years ago for tr2 but I think I have a different take.
First, I'd like to motivate the discussion with the limitations of the
current approach.
- For small types optional can double the size of storage
- Overhead can add up when stored in arrays (& most of it due to padding
if the sizeof(T) > 1).
- Cannot be used as a drop-in in a memory-mapped structure. In these
scenarios it's not uncommon to have a sentinel value.
- Cannot be used in as a drop-in in existing code that uses a sentinel
(i.e type-safety)
- Lots of overhead when a struct contains lots of optionals. For
example, protobuf uses bit-packing for this.
The main limitation, at least as I see it, of the Andrzej's traits
implementation is that it cannot be customized per-instance of optional.
This is probably fine for user-defined types but makes this optimization
not possible for built-in types. It's not uncommon to have only certain
instances of built-in types have invalid bit-patterns (e.g. NaN for double,
maximum value for size_t as reported by std::string).
To that end, my proposal to accomplish something like this would require
adding a secondary template parameter that defines the storage of the
initialization state. Here is a straw-man skeleton example of what the
std::optional class interface might look like. constexpr omitted for
simplicity but I don't see anything preventing it & optional_storage is the
hypothetical :
template <typename T, typename S = default_optional_initialization_storage>
class optional {
public:
optional(std::nullopt_t)
{
std::get<0>(_data).set_initialized(reinterpret_cast<T*>(&std::get<1
optional(const T&)
{
std::get<0>(_data).set_initialized(reinterpret_cast<T*>(&std::get<1
...
bool is_initialized() const
{
return std::get<0>(_data).is_initialized();
}
...
private:
std::tuple<S, aligned_storage_t<sizeof(T)>> _data;
};
default_optional_initialization_storage would comply with the interface for
optional_initialization_storage & look something like:
struct default_optional_initialization_storage {
template <typename T>
bool is_initialized(T*) const
{
return _initialized;
}
template <typename T>
void set_initialized(T*, bool initialized)
{
_initialized = initialized;
}
bool _initialized = false;
};
An example for hiding the state as via NaN for double:
struct nan_optional_storage {
bool is_initialized(double* value) const
{
return !std::isnan(*value)
}
void set_initialized(double* value, bool initialized)
{
if (!initialized) {
*value = std::numeric_limits<double>::quite_NaN();
}
}
};
Some of my thoughts on this sample code:
- It is by no means an exaustive implementation and I'm sure there's
lots of nitpicking to be done over details/naming/etc. I just want to get
a sense of does something like this even make sense.
- This is purely a mechanism through which optional can be optimized
with domain-specific knowledge. There is no optimization suggested for
built-in types (e.g. not even pointer types would optimize the nullptr case
by default).
- The sentinel-value use-case would probably be important enough that a
simple API for expressing such a sentinel value would be valuable
(something like optional<double, sentinel<double, nan("")>>)
- This is purely a customization point to apply domain-specific
knowledge to optimize optional. There is no optimization available by
default.
- The careful reader will note that the bit-packing case has not been
addressed. I am not quite certain how to actually achieve this since the
storage lives external to the optional<> itself. Passing through some kind
of ctx in all optional APIs?
- It has been suggested by some that this no longer represents the CS
concept of an optional monad and should be a distinct type. I'm not sure I
really see why (purity of mapping C++ to theoretical CS aside).
Thanks for reading,
Vitali
I'd like to bring up this topic again. I know Andrzej brought it up a
couple of years ago for tr2 but I think I have a different take.
First, I'd like to motivate the discussion with the limitations of the
current approach.
- For small types optional can double the size of storage
- Overhead can add up when stored in arrays (& most of it due to padding
if the sizeof(T) > 1).
- Cannot be used as a drop-in in a memory-mapped structure. In these
scenarios it's not uncommon to have a sentinel value.
- Cannot be used in as a drop-in in existing code that uses a sentinel
(i.e type-safety)
- Lots of overhead when a struct contains lots of optionals. For
example, protobuf uses bit-packing for this.
The main limitation, at least as I see it, of the Andrzej's traits
implementation is that it cannot be customized per-instance of optional.
This is probably fine for user-defined types but makes this optimization
not possible for built-in types. It's not uncommon to have only certain
instances of built-in types have invalid bit-patterns (e.g. NaN for double,
maximum value for size_t as reported by std::string).
To that end, my proposal to accomplish something like this would require
adding a secondary template parameter that defines the storage of the
initialization state. Here is a straw-man skeleton example of what the
std::optional class interface might look like. constexpr omitted for
simplicity but I don't see anything preventing it & optional_storage is the
hypothetical :
template <typename T, typename S = default_optional_initialization_storage>
class optional {
public:
optional(std::nullopt_t)
{
std::get<0>(_data).set_initialized(reinterpret_cast<T*>(&std::get<1
(_data)), false);
}optional(const T&)
{
std::get<0>(_data).set_initialized(reinterpret_cast<T*>(&std::get<1
(_data)), true);
}...
bool is_initialized() const
{
return std::get<0>(_data).is_initialized();
}
...
private:
std::tuple<S, aligned_storage_t<sizeof(T)>> _data;
};
default_optional_initialization_storage would comply with the interface for
optional_initialization_storage & look something like:
struct default_optional_initialization_storage {
template <typename T>
bool is_initialized(T*) const
{
return _initialized;
}
template <typename T>
void set_initialized(T*, bool initialized)
{
_initialized = initialized;
}
bool _initialized = false;
};
An example for hiding the state as via NaN for double:
struct nan_optional_storage {
bool is_initialized(double* value) const
{
return !std::isnan(*value)
}
void set_initialized(double* value, bool initialized)
{
if (!initialized) {
*value = std::numeric_limits<double>::quite_NaN();
}
}
};
Some of my thoughts on this sample code:
- It is by no means an exaustive implementation and I'm sure there's
lots of nitpicking to be done over details/naming/etc. I just want to get
a sense of does something like this even make sense.
- This is purely a mechanism through which optional can be optimized
with domain-specific knowledge. There is no optimization suggested for
built-in types (e.g. not even pointer types would optimize the nullptr case
by default).
- The sentinel-value use-case would probably be important enough that a
simple API for expressing such a sentinel value would be valuable
(something like optional<double, sentinel<double, nan("")>>)
- This is purely a customization point to apply domain-specific
knowledge to optimize optional. There is no optimization available by
default.
- The careful reader will note that the bit-packing case has not been
addressed. I am not quite certain how to actually achieve this since the
storage lives external to the optional<> itself. Passing through some kind
of ctx in all optional APIs?
- It has been suggested by some that this no longer represents the CS
concept of an optional monad and should be a distinct type. I'm not sure I
really see why (purity of mapping C++ to theoretical CS aside).
Thanks for reading,
Vitali
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.