There are several benefits coming with this approach:
The other extension, orthogonal to first one, is to allow value to be stored using alternate representation. As an example, reference type can be stored as a pointer and simply presented as reference. This will also allow to have cheap and relatively easy support for storing references in std::optional, without need of completely separate type specialization.
Any thoughts on this?
It has nothing to do with includes. Altering the definition of
optional to conditionally use
a trait would be remotely feasible, but it would also be an abi-breaking change.
At the moment, std::optional type, in the way it was accepted by committee, always has storage block for its value and a flag field, which determines if actual value is present.
Although, there are often safe wrapper types (like std::reference_wrapper, or wrapper around OS handle, or a raw pointer) which have kind of "impossible state" on their own. For such types, it's perfectly possible to represent std::nullopt state as this "impossible" value:
- Allow to define optional sentinel value for a type. Current approach in STL seems to be trait struct for the type and trait adaptor used in std::optional to access values (similar to std::allocator<T> + std::allocator_traits<Alloc>). There might be a requirement that sentinel value should be constexpr.
- If type defines sentinel value, then std::optional's nullopt is represented with that sentinel value, and there's no additional flag
There are several benefits coming with this approach:
- Space efficiency for types with "invalid value"
- Type safety. Sentinel value simply cannot be used the same way as normal value. Roughly saying, no "null pointer dereference" issue
The other extension, orthogonal to first one, is to allow value to be stored using alternate representation. As an example, reference type can be stored as a pointer and simply presented as reference. This will also allow to have cheap and relatively easy support for storing references in std::optional, without need of completely separate type specialization.
Mark Zeren's C++Now talk on strings will be interesting to you. Following that talk, I implemented his ideas for a "nullable trait" in my "STL From Scratch" under the name std::tombstone_traits<T>.
Here are the problems with your approach:- Using a sentinel value of the type T is philosophically incorrect w.r.t. lifetime management. If I construct a disengaged optional, and then destroy it, I must not call the destructor of T. If I copy a disengaged optional, I must not call the copy constructor of T. If you unconditionally construct a T no matter whether the optional is engaged or disengaged, then what you have is philosophically not an optional<T>; it is literally a T. If that's what you want, just write T.
- Using a sentinel value of type T is technically impossible if the type T has no "available" value; for example, bool. However, it is common for there to be "available" bit-patterns that are distinguishable from any possible value of T. You raised the example of a reference_wrapper<T> in the all-bits-zero state. For another example, there's std::vector in the state "ptr=null, size=42, capacity=0" — that's a bit-pattern that does not correspond to any value of type std::vector.
- In fact, if you can enumerate the available bit-patterns, you can store a lot of extra information in the unused bits of a T. My STL uses this fact to make sizeof(optional<optional<optional<bool>>>) == 1. Your approach would be completely unable to optimize this; you'd have to use at least 4 bytes.
The other extension, orthogonal to first one, is to allow value to be stored using alternate representation. As an example, reference type can be stored as a pointer and simply presented as reference. This will also allow to have cheap and relatively easy support for storing references in std::optional, without need of completely separate type specialization.
This is optional<reference_wrapper<T>>.As for why optional<T&> is a terrible, horrible, no-good, very bad idea, please see Matt Calabrese's talk from C++Now.
HTH,Arthur
On 2017-06-30 10:21, Igor Baidiuk wrote:
> On Friday, June 30, 2017 at 2:05:35 AM UTC+3, Arthur O'Dwyer wrote:
>> - In fact, if you can enumerate the available bit-patterns, you can store
>> a *lot* of extra information in the unused bits of a T. My STL uses this
>> fact to make sizeof(optional<optional<optional<bool>>>) == 1. Your
>> approach would be completely unable to optimize this; you'd have to use at
>> least 4 bytes.
>
> Let's imagine someone tries to take reference to doubly-optional bool from
> your triple-optional bool. And assign it some value.
I can imagine ways this can still work. (The complexity will depend on
what happens when you assign `true` to the inner-most `bool`, i.e. if it
stores 1 (0x1) or -1 (0xff).)
> Also note that
> Some(Some(Some(false))), Some(Some(None)), Some(None) are three different
> state of your triply-optional bool. Even with space compression, you'll
> need at least 2 bytes.
Er... why?
An `optional<T>` needs `bitsof(T)+1` bits of storage. A `bool` has two
legal states, so `bitsof(bool) == 1`. Thus, an `optional<bool>` needs 2
bits of storage.
Applying this recursively:
bitsof(optional<bool>) == 2
bitsof(optional<optional<bool>>) == 3
bitsof(optional<optional<optional<bool>>>) == 4
Last I checked, there are (probably / at least) 8 bits in a byte. I
hardly need *16* bits to store optional^3<bool>. In fact, I could
(theoretically) store up to optional^7<bool> in *one* byte.
On Friday, June 30, 2017 at 2:05:35 AM UTC+3, Arthur O'Dwyer wrote:Mark Zeren's C++Now talk on strings will be interesting to you. Following that talk, I implemented his ideas for a "nullable trait" in my "STL From Scratch" under the name std::tombstone_traits<T>.Thanks, will check it.
Here are the problems with your approach:- Using a sentinel value of the type T is philosophically incorrect w.r.t. lifetime management. If I construct a disengaged optional, and then destroy it, I must not call the destructor of T. If I copy a disengaged optional, I must not call the copy constructor of T. If you unconditionally construct a T no matter whether the optional is engaged or disengaged, then what you have is philosophically not an optional<T>; it is literally a T. If that's what you want, just write T.
I disagree.
Following your thought, C++ is already incorrect w.r.t. lifetimes - some value, which is moved out, is still accessible and requires destructor call.
Next, I'm thinking more from correctness and usability points of view.
The values used for sentinels are usually "invalid" type states. The closest example is what remains of value if it's moved out. In such case destructor is trivial.
Next, such "invalid" state simply cannot be used in place of normal value. Null pointer cannot be used to access object. In fact, pointers should have never been allowed to be dereferenced, from philosophical correctness point of view. Typed pointer is kind of abomination - it can be rebound to arbitrary location via simple arithmetic operations, and it can be used to access data like reference.
16 because you need full byte object in optional otherwise you will get `std::vector<bool>` mk2.
struct optional
{
union { bool v; char i; };
bool isSet()
{
bool t = true, f = false;
return memcmp(this, &t, 1) || memcmp(this, &f, 1);
}
}
--
You received this message because you are subscribed to a topic in the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this topic, visit https://groups.google.com/a/isocpp.org/d/topic/std-proposals/fghaQHk8oe8/unsubscribe.
To unsubscribe from this group and all its topics, send an email to std-proposals+unsubscribe@isocpp.org.
To post to this group, send email to std-pr...@isocpp.org.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/b2977aad-1723-42b6-ae4e-5724ad7cbf17%40isocpp.org.
This is optional<reference_wrapper<T>>.As for why optional<T&> is a terrible, horrible, no-good, very bad idea, please see Matt Calabrese's talk from C++Now.
On 2017-06-30 01:05, Arthur O'Dwyer wrote:
[...]
Okay, now for the problems with _my_ approach:
- The implementation of tombstone_traits<T> might need to be a friend
of T, and certainly needs to be kept in sync with the implementation
details of T, which is too bad, but not fatal.
For a library-only solution, this is true. But a solution for the standard could use compiler hooks to identify fixed-value bits (e.g. LSBs of a sizeof(T) > 1 T*, MSBs of T*s which are outside the target architecture's physical address space, ...) or corresponding bits of objects containing such T*s) into which to stuff side information.
- Because this approach relies on a kind of type-punning — a safe
kind, AFAIK, but a kind — therefore it cannot be done constexpr,
which means std::optional<T> is no longer usable in constexpr
contexts. IMO this is absolutely fatal for pursuing this approach in
the STL. (Unless someone can come up with a way of doing it constexpr
that I didn't think of...)
Again, for a library-only solution, this is true (modulo your last qualification). But as a std library feature, we could just *define* constexpr functions that extract the side information from a type, to be implemented as compiler hooks, if necessary (GCC seems to allow type punning in constexpr functions already as an extension, so there it would be library-only):
constexpr const T &get_object(const T &) noexcept;
constexpr T &get_object(T &) noexcept;
constexpr sideband<T> get_sideband(const T &) noexcept;
where sideband<T> is some kind of integral type that maps the information that can be stored in the fixed-value bits of T into an integer range [0, N[, get_object launders its argument from the sideband information (masks the sideband bits of a T*), and get_sideband launders its argument from the type's value-representing bits.
This exposes the sideband information storable in any T to library writers (not just std::optional, but std::variant, too, and even Boost and Qt).
The other extension, orthogonal to first one, is to allow value to be stored using alternate representation. As an example, reference type can be stored as a pointer and simply presented as reference. This will also allow to have cheap and relatively easy support for storing references in std::optional, without need of completely separate type specialization.
This is optional<reference_wrapper<T>>.As for why optional<T&> is a terrible, horrible, no-good, very bad idea, please see Matt Calabrese's talk from C++Now.