Date: Sat, 07 Jun 2025 20:48:45 +0300
On Sat, 2025-06-07 at 19:15 +0200, Jan Schultke wrote:
> > Why wouldn't it work with fundamental types?
> > I explicitly marked them as supporting the protocol. Maybe I did a
> > bad job of explaining myself.
>
> Because every possible value of type int is not an empty
> std::optional
> when you put it into std::optional already. You cannot define int(-1)
> to be an empty std::optional after the fact, so you're stuck with
> effectively storing an extra bool.
>
> This behavior can obviously not be changed at this point.
But that is not my proposal. std::optional keeps its bool flag. The
difference is in how the special member functions are generated:
std::optional<T>::~optional() {
if constexpr (uses the dummy protocol) {
_value->~T();
} else {
if (_engaged) {
_value->~T();
}
}
}
We avoided a conditional. For fundamental types, we didn't win
anything, but (say) the move constructor becomes simpler:
std::optional<T>::optional(optional&& o) {
// omitting exception safety
if constexpr (uses the dummy protocol) {
_engaged = std::exchange(o._engaged, false);
_value = std::move(o._value);
} else {
_engaged = std::exchange(o._engaged, false);
if (_engaged) {
// Can be replaced with std::relocate_at()
std::construct_at(&_value, std::move(o._value));
std::destroy_at(&o._value);
}
}
}
For fundamental types, and for trivially relocatable types, moving an
optional becomes just data movement with no conditionals.
> On Sat, 7 Jun 2025 at 19:12, Avi Kivity <avi_at_[hidden]> wrote:
> >
> > Why wouldn't it work with fundamental types? I explicitly marked
> > them as supporting the protocol. Maybe I did a bad job of
> > explaining myself.
> >
> > And again some raises std::expected, it's not related. This is for
> > every container that can conditionally carry a value, std::optional
> > is the best example but I encountered the problem while working on
> > a different class (an interval type).
> >
> > On Sat, 2025-06-07 at 13:24 +0200, Jan Schultke wrote:
> >
> > I don't think it's worth the effort if it cannot work for
> > fundamental
> > types, and the ABI for std::optional is set in stone. Ideally, we
> > would be able to say that std::optional<X*> where nullptr stores
> > the
> > empty value, or std::optional<int> where -1 represents an empty
> > value,
> > etc.
> >
> > Those things seem feasible with std::expected with a bit of library
> > support. We could have something like
> >
> > std::expected<int, std::nonvalue<-1>>
> >
> >
> > Which would use the value "-1" to represent an unexpected object.
> > This
> > would also not break any existing code since it would use novel
> > types
> > and a novel protocol, even in the case of fundamental types.
> >
> >
> > Why wouldn't it work with fundamental types?
> > I explicitly marked them as supporting the protocol. Maybe I did a
> > bad job of explaining myself.
>
> Because every possible value of type int is not an empty
> std::optional
> when you put it into std::optional already. You cannot define int(-1)
> to be an empty std::optional after the fact, so you're stuck with
> effectively storing an extra bool.
>
> This behavior can obviously not be changed at this point.
But that is not my proposal. std::optional keeps its bool flag. The
difference is in how the special member functions are generated:
std::optional<T>::~optional() {
if constexpr (uses the dummy protocol) {
_value->~T();
} else {
if (_engaged) {
_value->~T();
}
}
}
We avoided a conditional. For fundamental types, we didn't win
anything, but (say) the move constructor becomes simpler:
std::optional<T>::optional(optional&& o) {
// omitting exception safety
if constexpr (uses the dummy protocol) {
_engaged = std::exchange(o._engaged, false);
_value = std::move(o._value);
} else {
_engaged = std::exchange(o._engaged, false);
if (_engaged) {
// Can be replaced with std::relocate_at()
std::construct_at(&_value, std::move(o._value));
std::destroy_at(&o._value);
}
}
}
For fundamental types, and for trivially relocatable types, moving an
optional becomes just data movement with no conditionals.
> On Sat, 7 Jun 2025 at 19:12, Avi Kivity <avi_at_[hidden]> wrote:
> >
> > Why wouldn't it work with fundamental types? I explicitly marked
> > them as supporting the protocol. Maybe I did a bad job of
> > explaining myself.
> >
> > And again some raises std::expected, it's not related. This is for
> > every container that can conditionally carry a value, std::optional
> > is the best example but I encountered the problem while working on
> > a different class (an interval type).
> >
> > On Sat, 2025-06-07 at 13:24 +0200, Jan Schultke wrote:
> >
> > I don't think it's worth the effort if it cannot work for
> > fundamental
> > types, and the ABI for std::optional is set in stone. Ideally, we
> > would be able to say that std::optional<X*> where nullptr stores
> > the
> > empty value, or std::optional<int> where -1 represents an empty
> > value,
> > etc.
> >
> > Those things seem feasible with std::expected with a bit of library
> > support. We could have something like
> >
> > std::expected<int, std::nonvalue<-1>>
> >
> >
> > Which would use the value "-1" to represent an unexpected object.
> > This
> > would also not break any existing code since it would use novel
> > types
> > and a novel protocol, even in the case of fundamental types.
> >
> >
Received on 2025-06-07 17:48:49