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;
};
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:I'd be happy to see a paper fleshing this out. I don't think anyone
was opposed to the idea, but it would have slowed down the initial
optional<> proposal if it had been included.
On 24 June 2015 at 17:58, 'Jeffrey Yasskin' via ISO C++ Standard - Future Proposals <std-pr...@isocpp.org> wrote:
I'd be happy to see a paper fleshing this out. I don't think anyone
was opposed to the idea, but it would have slowed down the initial
optional<> proposal if it had been included.
Ahem. I was strongly against it when it came up on this list two years ago, and I'm still strongly against it now. This could be slightly tapered by proposing a separate type instead of shoehorning it into optional.
If it were to be shoehorned into optional, I'd be strongly against optional going into C++17, and that would make me sad.
Either way, I believe that it would take a significant amount of committee time to discuss. You have to revisit every issue that optional and variant touched,
including the issues with the empty state of variant, because now you may not be able to no throw construct the disengaged state.
--
Are you only against it because it might delay optional, or for technical/design reasons?
constexpr optional() noexcept;
constexpr optional(nullopt_t) noexcept;
optional<T>& operator=(nullopt_t) noexcept;
Besides the noexcept issue, what state is the optional left in when an exception is thrown? Beats me.
And those are the simpler things. We haven't even gotten to the more controversial stuff, like heterogeneous comparisons and ordering.
And there may language issues. As proposed:
template <typename T>
void set_initialized(T*,
bool
initialized
)On 25 June 2015 at 09:31, Tony V E <tvan...@gmail.com> wrote:Are you only against it because it might delay optional, or for technical/design reasons?Both.If optional significantly changes, then any user experience we have so far gets thrown out the window. I'm fine if that happens because of the user experience, but I'm not fine with that for the purposes of invention. Especially if optional is a vocabulary type being targeted for C++17.As for technical considerations:One of the mental models we used for designing optional was that it is a T with an extra state. This customization breaks that model.We would have to revisit every single line of optional to see if it still holds true.For purposes of this, lets assume I want an optional<string> where string("Geronimo") is the unengaged state.Let us start with the simplest constructors:constexpr optional() noexcept;
constexpr optional(nullopt_t) noexcept;
They are obviously no longer noexecept(true). Do we make that conditionally noexcept? If so, how? This customization will be as complicated as allocators (such as a noexcept_on_nullopt_t_construction_or_assignment member type derived from bool_type).How about assignment? Takeoptional<T>& operator=(nullopt_t) noexcept;
Besides the noexcept issue, what state is the optional left in when an exception is thrown? Beats me.
And those are the simpler things. We haven't even gotten to the more controversial stuff, like heterogeneous comparisons and ordering.
And there may language issues. As proposed:template <typename T>
void set_initialized(
T*,
)
initialized
bool
The T* being passed may be a pointer to uninitialized storage and not a T. Is that legal?
LEWG likes to "time box" discussions (which I strongly disagree with, because it basically means we aren't giving the author enough feedback to make useful progress, making it poor use of both the author's time and the committee's time. If we don't have the time to adequately discuss things, why are we soliciting for more stuff?). Based on how much committee time has been and will be spent on optional and variant, do you really think we can get through this kind of proposal in only an hour or two? I don't.
Now, a significant portion of the complexity goes away if it is a separate type, because a separate type can just store a T instead of a block of storage it has to manage, the ordering can be identical to T, etc.
I’m not as familiar with the issues so I’m probably misunderstanding your concern. Is it that every custom specialization of optional must be able to no-throw construct the disengaged state?
And those are the simpler things. We haven't even gotten to the more controversial stuff, like heterogeneous comparisons and ordering.Can you clarify why you think those would be affected? I'm sure I'm missing something obvious. It seems to me like whatever comparison/ordering is defined currently would work equally since it has to take into account the disengaged state which is now accessible via a function call instead of assuming it's in a separate boolean.
And there may language issues. As proposed:template <typename T>
void set_initialized(
T*,
)
initialized
bool
The T* being passed may be a pointer to uninitialized storage and not a T. Is that legal?That was just a strawman. I'm still mulling over how it would actually be implemented & it's definitely. It would be good to know if this is illegal; at first glance it doesn't seem so since it doesn't seem uncommon to have a pointer to uninitialized storage (e.g. std::vector after a reserve).
Perhaps after I’ve done steps 2 & 3 we can see if the process has been completed in time for submitting it for Kona & whether the proposal is as complicated as you fear?
I certainly appreciate your feedback. I hadn't thought about noexcept & I'll make sure to incorporate it in the proposal. Are there any issues you forsee?
On 25 June 2015 at 12:58, <vlo...@gmail.com> wrote:I’m not as familiar with the issues so I’m probably misunderstanding your concern. Is it that every custom specialization of optional must be able to no-throw construct the disengaged state?No, but you weren't proposing to specialize optional. You were proposing to add a second policy-like template parameter to optional. Those are two very different things.If you are specializing it, I really have to question why it has to be spelled o-p-t-i-o-n-a-l, as it really isn't substitutable in generic constructs (which is the main reason to have them spelled the same way). Shades of vector<bool> all over again...
And those are the simpler things. We haven't even gotten to the more controversial stuff, like heterogeneous comparisons and ordering.Can you clarify why you think those would be affected? I'm sure I'm missing something obvious. It seems to me like whatever comparison/ordering is defined currently would work equally since it has to take into account the disengaged state which is now accessible via a function call instead of assuming it's in a separate boolean.The disengaged state is smaller than every state of T, in both homogeneous (optional<T> vs optional<T>) and heterogeneous (optional<T> vs T) comparisons. Do you intend for that to hold, in which case comparing two engaged optionals now behaves differently than comparing the values they hold.
bool operator==(const optional<T, P1>& lhs, const optional<T, P2>& rhs)
{
if (!lhs.is_initialized() ^ rhs.is_initialized()) {
return false;
}
if (lhs.is_initialized() && rhs.is_initialized()) {
return *lhs == *rhs;
}
return true;
}
And there may language issues. As proposed:template <typename T>
void set_initialized(
T*,
)
initialized
bool
The T* being passed may be a pointer to uninitialized storage and not a T. Is that legal?That was just a strawman. I'm still mulling over how it would actually be implemented & it's definitely. It would be good to know if this is illegal; at first glance it doesn't seem so since it doesn't seem uncommon to have a pointer to uninitialized storage (e.g. std::vector after a reserve).A pointer, such as char* or void*, to uninitialized storage is okay; I'm not sure about a pointer of type T* to uninitialized storage.
Perhaps after I’ve done steps 2 & 3 we can see if the process has been completed in time for submitting it for Kona & whether the proposal is as complicated as you fear?Are you planning on coming to Kona to present it? If not, my advice is to get someone who was involved with the discussions around optional to do a deep dive into your proposal and champion it. Jeffrey and Tony are two good candidates, if they are going and you can convince them.
On Thursday, June 25, 2015 at 11:54:07 AM UTC-7, Nevin ":-)" Liber wrote:On 25 June 2015 at 12:58, <vlo...@gmail.com> wrote:I’m not as familiar with the issues so I’m probably misunderstanding your concern. Is it that every custom specialization of optional must be able to no-throw construct the disengaged state?No, but you weren't proposing to specialize optional. You were proposing to add a second policy-like template parameter to optional. Those are two very different things.If you are specializing it, I really have to question why it has to be spelled o-p-t-i-o-n-a-l, as it really isn't substitutable in generic constructs (which is the main reason to have them spelled the same way). Shades of vector<bool> all over again...Well to get an optimized policy, the user would have to specialize it with their user-defined policy object. Is that not the right terminology?
In any case what I meant to say is that does every possible policy need to meet these criteria?
The disengaged state is smaller than every state of T, in both homogeneous (optional<T> vs optional<T>) and heterogeneous (optional<T> vs T) comparisons. Do you intend for that to hold, in which case comparing two engaged optionals now behaves differently than comparing the values they hold.I'm probably being a little thick. Can you clarify with the following implementation of equality where you would see a problem with the implementation?
bool operator==(const optional<T, P1>& lhs, const optional<T, P2>& rhs)
{
if (!lhs.is_initialized() ^ rhs.is_initialized()) {
return false;
}
if (lhs.is_initialized() && rhs.is_initialized()) {
return *lhs == *rhs;
}
return true;
}
I have nothing against coming to Kona. I would certainly be interested in presenting it but I can also work to get champions too. I haven't even presented a draft of a proposal so perhaps discussing Kona is a bit premature?
For all I know there are intractable problems that come up when I flush it out fully.
My general advice is that people attend a meeting or two before making a proposal, so that can see what they are in for. Roughly speaking, it's like defending a dissertation in front of a panel of people who don't really care if you graduate. You are the domain expert fielding tough questions from other (domain and non-domain) experts, as well as from people like me.I do realize that it sometimes takes a proposal to get an employer to send someone to a meeting, in which case I advise not picking something too ambitious. IMO, this is a very ambitious proposal. Of course, other committee members may feel otherwise and be willing to put in the preparatory effort so that it gets a fair chance. We are all volunteers and speak only for ourselves or those we represent, as no one speaks for the committee.
On 25.06.2015 03:47, Nevin Liber wrote:
I don't think this is a fair characterization. There are some interactions and probably subtleties that need to be disscussed, but hardly every interaction. It is mostly an implementation detail.Either way, I believe that it would take a significant amount of committee time to discuss. You have to revisit every issue that optional and variant touched,
optional<size_t> x;
// ...
x = function_returning_a_size_t();
On 25 June 2015 at 14:22, <vlo...@gmail.com> wrote:
On Thursday, June 25, 2015 at 11:54:07 AM UTC-7, Nevin ":-)" Liber wrote:On 25 June 2015 at 12:58, <vlo...@gmail.com> wrote:I’m not as familiar with the issues so I’m probably misunderstanding your concern. Is it that every custom specialization of optional must be able to no-throw construct the disengaged state?No, but you weren't proposing to specialize optional. You were proposing to add a second policy-like template parameter to optional. Those are two very different things.If you are specializing it, I really have to question why it has to be spelled o-p-t-i-o-n-a-l, as it really isn't substitutable in generic constructs (which is the main reason to have them spelled the same way). Shades of vector<bool> all over again...Well to get an optimized policy, the user would have to specialize it with their user-defined policy object. Is that not the right terminology?vector<bool> is a specialization of vector, and has a different interface than vector. That is very different than, say, providing an allocator (which is basically a policy) to a vector.In any case what I meant to say is that does every possible policy need to meet these criteria?It is in the interface for optional, so either it does or you change the interface. You get to explore the design space.The disengaged state is smaller than every state of T, in both homogeneous (optional<T> vs optional<T>) and heterogeneous (optional<T> vs T) comparisons. Do you intend for that to hold, in which case comparing two engaged optionals now behaves differently than comparing the values they hold.I'm probably being a little thick. Can you clarify with the following implementation of equality where you would see a problem with the implementation?
bool operator==(const optional<T, P1>& lhs, const optional<T, P2>& rhs)
{
if (!lhs.is_initialized() ^ rhs.is_initialized()) {
return false;
}
if (lhs.is_initialized() && rhs.is_initialized()) {
return *lhs == *rhs;
}
return true;
}Say you use NaN for your disengaged value.float f = std::numeric_limits<float>::quiet_NaN();assert(!(f==f));assert(!(optional<float>(f) == optional<float>(f))); // fails
assert(!(optional<float>(f) == f)); // fails
assert(!(f == optional<float>(f))); // failsNow, I'm perfectly willing (although others on the committee are not) to say that non-regular types such as float are insane, and equality comparisons aren't a problem for regular types.However, ordered comparisons are different. Using string("Geronimo") as the disengaged value:string s = "Geronimo";string t = "Geronim";assert(t < s);assert(optional<string>(t) < optional<string>(s)); // failsassert(optional<string>(t) < s); // failsassert(t < optional<string>(s)); // failsIs that the desired behavior? As part of your proposal, you explore the design space, pick something, and then LEWG will debate it if they really want the feature.
On 25 June 2015 at 12:58, <vlo...@gmail.com> wrote:
Are you planning on coming to Kona to present it? If not, my advice is to get someone who was involved with the discussions around optional to do a deep dive into your proposal and champion it. Jeffrey and Tony are two good candidates, if they are going and you can convince them.
However, ordered comparisons are different. Using string("Geronimo") as the disengaged value:string s = "Geronimo";string t = "Geronim";assert(t < s);assert(optional<string>(t) < optional<string>(s)); // failsassert(optional<string>(t) < s); // failsassert(t < optional<string>(s)); // failsIs that the desired behavior? As part of your proposal, you explore the design space, pick something, and then LEWG will debate it if they really want the feature.
Yes it is. The sentinel value is defined to be the disengaged state & cannot be represented as a valid value. If the above assertions are unexpected then the policy
is incorrectly implemented.
optional<double, nan_optional_storage> od {2.3};
double d = compute_val();
assert (od); // contains value
*od = d;
assert (od); // contains value?
The second assertion always holds in normal optional. In the "optimized" version, it depends on the value of d.
The main criticism of this proposal was that it affects the optional's semantics:
optional<double, nan_optional_storage> od {2.3};
double d = compute_val();
assert (od); // contains value
*od = d;
assert (od); // contains value?
The second assertion always holds in normal optional. In the "optimized" version, it depends on the value of d.
Any type where at least one bit pattern isn't used in a valid object.>> I have used an 'optimised optional' of the type suggested in this
>> paper for years. At some point I decided to forbid this case -- a user
>> should be completely unable to tell any space optimisation is being
>> performed (except the object is smaller obviously). That does mean you
>> can't compress types such as int or double.
>>
>> Chris
>
>
> In what cases *can* you compress then?
Personally, I mainly compress containers (std::vector, std::list,
std::map, etc.).
I know on any system I care about malloc is never going to return
(void*)1, so any object which contains a pointer can assign that value
to a pointer to represent disengaged.
Chris
I certainly wouldn't compress optional<void*> for the reasons you outline, but within STD::vector, I might know more, such as promises about the alignment of memory, or knowledge of STD::allocator.
In the case of vector, in GCC there are 3 pointers start, end, capacity which satisfy start <= end <= capacity. I can therefore easily make a never occurring state by assigning start > end.
I am going to think more carefully about the nan-compression case given earlier. I would not put it in the standard, but I can see the benefit of letting users write it -- if they then assign a nan they are in trouble, but that is their own fault!
On 2015–06–28, at 3:47 AM, vlo...@gmail.com wrote:Hey David, I really like the idea of a basic_optional a lot better & can provide a better foundation for doing something like compressing the optional state for many optionals into a bit-field (e.g. protobuf) which is one the problems left unsolved.
I also like the idea of encapsulating the sentinel'ness as you propose (e.g. std::optional<never_quiet_nan<double>>) instead of having a separate traits. Can you elaborate a bit on how this could be accomplished? Right now optional has a boolean
field. My original design moved that boolean field into the policy/traits & used the empty base-class optimization to get rid of it when unnecessary. In the std::optional<never_quiet_nan<double>> example, would never_quiet_nan have a static constexpr member
boolean that said "I understand how to read/write my state from the value"? Or would there be an ADL traits class that parametrized this?
The std::vector & polymorphism cases I think are less compelling for as-if as I can't imagine a use-case where you would store either in optional. std::vector has a natural empty state & using optional<vector<X>> seems more likely to be a poor design
than anything else.
Polymorphic classes tend to be prone to slicing when used as a value-type. optional for enum classes (enums too?) would be possible if there were reflection so that you could query for an out-of-range value to use as a sentinel.
As you say, an implementation would be free to optimize these if it felt important under the as-if rule so there would be no need to specify it in the proposal.
I was hoping to get to writing up the proposal this weekend but it looks like probably it will end up next weekend. If you have any more thoughts on the matter feel free to email me directly.
On 2015–06–28, at 3:47 AM, vlo...@gmail.com wrote:Hey David, I really like the idea of a basic_optional a lot better & can provide a better foundation for doing something like compressing the optional state for many optionals into a bit-field (e.g. protobuf) which is one the problems left unsolved.
I also like the idea of encapsulating the sentinel'ness as you propose (e.g. std::optional<never_quiet_nan<double>>) instead of having a separate traits. Can you elaborate a bit on how this could be accomplished? Right now optional has a boolean
field. My original design moved that boolean field into the policy/traits & used the empty base-class optimization to get rid of it when unnecessary. In the std::optional<never_quiet_nan<double>> example, would never_quiet_nan have a static constexpr member
boolean that said "I understand how to read/write my state from the value"? Or would there be an ADL traits class that parametrized this?I’m thinking like this:// Thin wrapper:template< typename float_t >struct never_quiet_nan {float_t value = 0;operator float_t & () { return value; }operator float_t const & () const { return value; }};// ADL object representation functions:template< typename float_t >void invalidate_representation( never_quiet_nan< float_t > * invalid_ptr )
{ * reinterpret_cast< float_t * >( invalid_ptr ) = std::numeric_limits< float_t >::quiet_NaN; }template< typename float_t >bool is_valid_representation( never_quiet_nan< float_t > const * questionable_ptr )
{ return std::isnan( * reinterpret_cast< float_t const * >( questionable_ptr ) ); }
The std::vector & polymorphism cases I think are less compelling for as-if as I can't imagine a use-case where you would store either in optional. std::vector has a natural empty state & using optional<vector<X>> seems more likely to be a poor design
than anything else.One use-case of std::optional is for output parameters. Those are already an anti-pattern per se, but still I think a blanket statement that optional<vector> should never happen is too strong.Under as-if, it’s a question of how many priorities a standard library can implement before they need to freeze their ABI.Polymorphic classes tend to be prone to slicing when used as a value-type. optional for enum classes (enums too?) would be possible if there were reflection so that you could query for an out-of-range value to use as a sentinel.Just ask the user for invalidate_representation and is_valid_representation. Enumerations aren’t closed so reflection can’t really discover invalid values.As you say, an implementation would be free to optimize these if it felt important under the as-if rule so there would be no need to specify it in the proposal.
I was hoping to get to writing up the proposal this weekend but it looks like probably it will end up next weekend. If you have any more thoughts on the matter feel free to email me directly.My hands are full… I’m procrastinating here already :P .Good luck!
--
---
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/46J1onhWJ-s/unsubscribe.
To unsubscribe from this group and all its topics, send an email to std-proposal...@isocpp.org.
On 2015–06–29, at 12:19 AM, Vitali Lovich <vlo...@gmail.com> wrote:Hmmm... And how would optional decide what state it needs to store?