optional<bool>

2,092 views
Skip to first unread message

Tony V E

unread,
Aug 29, 2013, 6:49:03 PM8/29/13
to std-pr...@isocpp.org
optional<bool> has some issues which boost::optional<> avoided by saying "use boost::tribool instead" (this was seen in the original Boost review way back when).  But there is currently no std::tribool nor a proposal for one.  So I expect optional<bool> will be used instead in many cases.

Main issue: operator bool does not return the same thing as comparison to bool. ie

optional<bool> opt = false;

// A
if (!opt) {
  doSomething();
}

// B
if (opt == false)  {
   doSomething();
}


The above 2 if-blocks are not the same.  Same problem with checking against 'true' obviously.

Now, I don't really want to lose "if (opt)" doing an 'engaged' check *in general* for optional<T>, but I'm wondering if:

- is it possible to make these cases ambiguous for optional<bool> only.  ie instead of picking A or B it just doesn't compile.

- if it is possible, is that a palatable solution?  (Note that there are alternatives for both cases - ie an explicit is_engaged() function (I think, or whatever it is called) vs an explicit dereference - "*opt == false" or check + deref.

I suspect the only people that would do things explicitly would typically be template authors, who don't know whether optional<T> is optional<bool> or not.

Tony


Nicol Bolas

unread,
Aug 29, 2013, 7:56:24 PM8/29/13
to std-pr...@isocpp.org


On Thursday, August 29, 2013 3:49:03 PM UTC-7, Tony V E wrote:
optional<bool> has some issues which boost::optional<> avoided by saying "use boost::tribool instead" (this was seen in the original Boost review way back when).  But there is currently no std::tribool nor a proposal for one.  So I expect optional<bool> will be used instead in many cases.

Main issue: operator bool does not return the same thing as comparison to bool. ie

optional<bool> opt = false;

// A
if (!opt) {
  doSomething();
}

// B
if (opt == false)  {
   doSomething();
}


The above 2 if-blocks are not the same.  Same problem with checking against 'true' obviously.

Now, I don't really want to lose "if (opt)" doing an 'engaged' check *in general* for optional<T>, but I'm wondering if:

- is it possible to make these cases ambiguous for optional<bool> only.  ie instead of picking A or B it just doesn't compile.

Sure, it's possible. std::enable_if gimmicks can remove the `explicit operator bool` function if T == bool.
 
- if it is possible, is that a palatable solution?  (Note that there are alternatives for both cases - ie an explicit is_engaged() function (I think, or whatever it is called) vs an explicit dereference - "*opt == false" or check + deref.

Absolutely not.

optional<bool> is a perfectly legitimate construct, so long as you understand what it means and how to work with it. If you don't, then you'll have problems. But it's not our job to fix that; it's your job to know what you're doing.

Let's learn the lessons of `vector<bool>`: don't change the interface to different instantiations of a template. Not unless it's because the type T flat-out doesn't support that operation.

I'm not saying that I'd be against `tribool`. But we shouldn't damage or inhibit `optional` just because we don't have a `tribool` around. It's up to the individual programmers to decide which construct makes more sense to them: optional<bool> or tribool.

Bjorn Reese

unread,
Aug 30, 2013, 6:20:36 AM8/30/13
to std-pr...@isocpp.org
On 08/30/2013 12:49 AM, Tony V E wrote:

> way back when). But there is currently no std::tribool nor a proposal
> for one. So I expect optional<bool> will be used instead in many cases.

There is N2136, but I do not know its current status.

Tony V E

unread,
Aug 30, 2013, 5:09:52 PM8/30/13
to std-pr...@isocpp.org
- is it possible to make these cases ambiguous for optional<bool> only.  ie instead of picking A or B it just doesn't compile.

Sure, it's possible. std::enable_if gimmicks can remove the `explicit operator bool` function if T == bool.

removing the explicit operator would make A not compile, but B still compiles.  I'd prefer the other way around, or neither.
removing just the 'explicit' part would probably make A compile, and make B ambiguous, but I suspect it would have other unpleasant ramifications.
 
 
- if it is possible, is that a palatable solution?  (Note that there are alternatives for both cases - ie an explicit is_engaged() function (I think, or whatever it is called) vs an explicit dereference - "*opt == false" or check + deref.

Absolutely not.

optional<bool> is a perfectly legitimate construct, so long as you understand what it means and how to work with it. If you don't, then you'll have problems. But it's not our job to fix that; it's your job to know what you're doing.


I think it is our job to make APIs that are clear and usable. C++ already has enough gotchas.  If coding guidelines end up saying "always explicitly use opt != nullopt instead of just i (opt)", then we have failed.

 
Let's learn the lessons of `vector<bool>`: don't change the interface to different instantiations of a template. Not unless it's because the type T flat-out doesn't support that operation.


vector<bool> is a good point.  I don't think not-compiling is quite as extreme as vector<bool>, but I still agree your point has merit.

The alternative is to not do conversion to bool at all (for any T), ie force it to always be explicit opt == nullopt and opt != nullopt.  I think there is similarity here to if (ptr) vs if (ptr == nullptr) and other issues with 0 converting to/from pointers, and the need for nullptr.  Is optional<> making a similar mistake?

I'm not totally against if (opt) and actually use if (ptr) instead of if (ptr == nullptr), but I think it is important to understand the "cost" of optional's conversion operator.  I don't think the subtlety of optional<bool> was considered when optional was being voted on.  Maybe everyone is OK with it, but I just want to know they are *aware* of it.

Tony

Jeffrey Yasskin

unread,
Aug 30, 2013, 5:13:25 PM8/30/13
to std-pr...@isocpp.org
On Fri, Aug 30, 2013 at 2:09 PM, Tony V E <tvan...@gmail.com> wrote:
> The alternative is to not do conversion to bool at all (for any T), ie force
> it to always be explicit opt == nullopt and opt != nullopt. I think there
> is similarity here to if (ptr) vs if (ptr == nullptr) and other issues with
> 0 converting to/from pointers, and the need for nullptr. Is optional<>
> making a similar mistake?
>
> I'm not totally against if (opt) and actually use if (ptr) instead of if
> (ptr == nullptr), but I think it is important to understand the "cost" of
> optional's conversion operator. I don't think the subtlety of
> optional<bool> was considered when optional was being voted on. Maybe
> everyone is OK with it, but I just want to know they are *aware* of it.

It was mentioned in the paper
(http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3527.html#rationale.bool_conversion),
just not discussed to death like some other issues. I think people
were adequately aware of it.

Tony V E

unread,
Aug 30, 2013, 6:05:38 PM8/30/13
to std-pr...@isocpp.org
On Fri, Aug 30, 2013 at 5:13 PM, Jeffrey Yasskin <jyas...@google.com> wrote:
> Maybe everyone is OK with it, but I just want to know they are *aware* of it.

It was mentioned in the paper
(http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3527.html#rationale.bool_conversion),
just not discussed to death like some other issues.

:-)

 
I think people
were adequately aware of it.


I don't think the paper highlights the problem clearly.  ie the paper says

" types bool*, unique_ptr<bool>, shared_ptr<bool> suffer from the same potential problem,  "

yet they do not, because they do not have the mixed relational operators.

So for the above types,

    if (boolptr)

and

    if (boolptr == false)

*ARE* the same.

Yes, for the above types, there is some confusion as to

   if (boolptr)

did the programmer mean...

    if (*boolptr)

...?  But there isn't the flat-out "contradiction" of

     optional<bool> boolopt = false;

     assert( !boolopt != (boolopt == false) );

that only optional<bool> has.

(I think. Or did I miss mixed-relational-operators in smart pointers or something?)

Tony

Nicol Bolas

unread,
Aug 31, 2013, 1:45:03 AM8/31/13
to std-pr...@isocpp.org


On Friday, August 30, 2013 2:09:52 PM UTC-7, Tony V E wrote:

- is it possible to make these cases ambiguous for optional<bool> only.  ie instead of picking A or B it just doesn't compile.

Sure, it's possible. std::enable_if gimmicks can remove the `explicit operator bool` function if T == bool.

removing the explicit operator would make A not compile, but B still compiles. I'd prefer the other way around, or neither.
removing just the 'explicit' part would probably make A compile, and make B ambiguous, but I suspect it would have other unpleasant ramifications.
 
- if it is possible, is that a palatable solution?  (Note that there are alternatives for both cases - ie an explicit is_engaged() function (I think, or whatever it is called) vs an explicit dereference - "*opt == false" or check + deref.

Absolutely not.

optional<bool> is a perfectly legitimate construct, so long as you understand what it means and how to work with it. If you don't, then you'll have problems. But it's not our job to fix that; it's your job to know what you're doing.


I think it is our job to make APIs that are clear and usable. C++ already has enough gotchas.  If coding guidelines end up saying "always explicitly use opt != nullopt instead of just i (opt)", then we have failed.

It's not clear at all that any guidelines would ever say that.

If you in some template code that takes an `optional<T>`, why is it ever unreasonable to do this:

template<typename T>
void Func(const optional<T> &t)
{
 
if(t) {...}
}

This code always means the same thing no matter what T is. Similarly:

template<typename T>
void Func(const optional<T> &t)
{
 
if(t == false) {...}
}

will fail to compile for any T cannot be checked for equality against bools.The == operator doesn't force a contextual conversion to bool for the arguments; it will simply call the appropriate overloaded operator. The `operator==` for `optional<T>`, as defined in the standard, will check to see if `t` is disengagned. If it is, it returns false. If it isn't, it gets the T from it and performs operator== on the value being tested against. If that comparison is not supported, operator== cannot be instantiated and a compiler error results.

Again, this has well-defined behavior. For any T, it will test the internals of the optional against the value. There is therefore no expectation for this code to be testing only whether `t` is engaged or not.

So why would people suggest using `t != nullopt`?

Let's learn the lessons of `vector<bool>`: don't change the interface to different instantiations of a template. Not unless it's because the type T flat-out doesn't support that operation.


vector<bool> is a good point.  I don't think not-compiling is quite as extreme as vector<bool>, but I still agree your point has merit.

The alternative is to not do conversion to bool at all (for any T), ie force it to always be explicit opt == nullopt and opt != nullopt.

No. This issue is not substantial enough to completely break the `optional<T>` class just because some people might be confused when `T == bool`. `optional<T>` behaves like `optional<T>` for all types T. The behavior is only confusing for `bool` if you expect it to behave differently.

This "gotcha" is a misunderstanding of concepts, not a misunderstanding of implementation. And we should not break a class's interface because someone might misunderstand the concept of what it means for a boolean to be optional.

I think there is similarity here to if (ptr) vs if (ptr == nullptr) and other issues with 0 converting to/from pointers, and the need for nullptr.  Is optional<> making a similar mistake?

That's not why we have `nullptr`. We have `nullptr` because we needed a value which was:

1: a null-pointer constant.
2: not also an integer (and thus couldn't interfere in overload resolution with integers).
3: implicitly convertible to any pointer type.

No such value existed in C++98/03, so we had to invent one.

Tony V E

unread,
Aug 31, 2013, 4:12:28 PM8/31/13
to std-pr...@isocpp.org
On Sat, Aug 31, 2013 at 1:45 AM, Nicol Bolas <jmck...@gmail.com> wrote:


I think it is our job to make APIs that are clear and usable. C++ already has enough gotchas.  If coding guidelines end up saying "always explicitly use opt != nullopt instead of just i (opt)", then we have failed.

It's not clear at all that any guidelines would ever say that.

I've unfortunately had to read a lot of guidelines.  Many insist on if (ptr == nullptr) over if (ptr), and some even if (var == false) over if (!var).   (As it is easier to see.  Some also suggest if (not var) for similar reasons.)
So I suspect some will suggest if (opt == nullopt) regardless of any worries about optional<bool>.

There have also been requests for an explicit is_engaged() for optional, for similar reasons.  (I think opt != nullopt should be good enough for them.)



If you in some template code that takes an `optional<T>`, why is it ever unreasonable to do this:

template<typename T>
void Func(const optional<T> &t)
{
 
if(t) {...}
}

This code always means the same thing no matter what T is.

Agreed.  I guess template code should be fine.

 
Similarly:

template<typename T>
void Func(const optional<T> &t)
{
 
if(t == false) {...}
}

will fail to compile for any T cannot be checked for equality against bools.The == operator doesn't force a contextual conversion to bool for the arguments; it will simply call the appropriate overloaded operator. The `operator==` for `optional<T>`, as defined in the standard, will check to see if `t` is disengagned. If it is, it returns false. If it isn't, it gets the T from it and performs operator== on the value being tested against. If that comparison is not supported, operator== cannot be instantiated and a compiler error results.

Note that currently, optional<string>() == "foo" is not supported (the templated function doesn't match).  I think there will be a proposal to allow that.
Once there is, note that  optional<int>() == false *will* work, as int == bool is valid.

 

Again, this has well-defined behavior. For any T, it will test the internals of the optional against the value. There is therefore no expectation for this code to be testing only whether `t` is engaged or not.

So why would people suggest using `t != nullopt`?

Let's learn the lessons of `vector<bool>`: don't change the interface to different instantiations of a template. Not unless it's because the type T flat-out doesn't support that operation.


vector<bool> is a good point.  I don't think not-compiling is quite as extreme as vector<bool>, but I still agree your point has merit.

The alternative is to not do conversion to bool at all (for any T), ie force it to always be explicit opt == nullopt and opt != nullopt.

No. This issue is not substantial enough to completely break the `optional<T>` class just because some people might be confused when `T == bool`. `optional<T>` behaves like `optional<T>` for all types T. The behavior is only confusing for `bool` if you expect it to behave differently.

"This issue is not substantial enough".

I can understand that opinion.  If everyone agrees "meh, it is not a big deal" then OK.
 

This "gotcha" is a misunderstanding of concepts, not a misunderstanding of implementation. And we should not break a class's interface because someone might misunderstand the concept of what it means for a boolean to be optional.



I just find it odd that we took what seems to be a good interface, but ended up with

if (foo)

!=

if (foo == true)


If everyone is comfortable with that, OK then.  One more bit of irony; fine.  I just wanted to see if there were ways to avoid it without a high cost to the rest of optional.  Maybe there isn't.


Tony




--
 
---
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-proposal...@isocpp.org.
To post to this group, send email to std-pr...@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.

Andrzej Krzemieński

unread,
Sep 2, 2013, 4:48:42 AM9/2/13
to std-pr...@isocpp.org

Your analysis is correct. std::optional is the only type in STD that displays these semantics.

The "mixed" comparison (between optional<T> and T) is a natural consequence of allowing the implicit construction of optional<T> from T (the comparison then works immediately and one would have to artificially "poison" the rel-ops). In order to gracefully fix the problem you see, we would have to prevent the implicit conversion from T to optional<T>, which we believed was too drastic reduction of the interface.

Regards,
&rzej

Zhihao Yuan

unread,
Sep 3, 2013, 12:35:25 PM9/3/13
to std-pr...@isocpp.org

On Sep 2, 2013 4:48 AM, "Andrzej Krzemieński" <akrz...@gmail.com> wrote:
> The "mixed" comparison (between optional<T> and T) is a natural consequence of allowing the implicit construction of optional<T> from T (the comparison then works immediately and one would have to artificially "poison" the rel-ops).

I agree.  However, I don't think the explicit bool
operator is natural.  opt.empty() looks less
confusing IMHO.  100% copy of a pointer-like
interface is not needed.  For example, iterator is
also pointer-like, but it has no such interface.

Nicol Bolas

unread,
Sep 3, 2013, 6:57:18 PM9/3/13
to std-pr...@isocpp.org

`iterator` doesn't have that interface because it can't have that interface. It can't detect whether it's out of range, valid, or empty at all.

There are plenty of types in the standard library that detect validity by `explicit operator bool`.

Andrzej Krzemieński

unread,
Sep 4, 2013, 9:36:18 AM9/4/13
to std-pr...@isocpp.org

Agreed. explicit operator bool is a well established idiom in C++, and removing it would severely limit the interface of std::optional. We expect the following usages:

if (std::optional<int> oi = getValue())
  use (*oi);
else
  dosomethingElse ();

Personally, I would be more comfortable removing the assignment, conversion and comparison with T. But we are talking about preferences now, I guess.

As a side note, but in response to the original question: recently, I had to use boost::optional<bool> to indicate a bool that has a determinate value which has not yet been computed/assigned. boost::tribool was not the right abstraction, because logical operators have inadequate semantics for my usage. At some point I wanted to check if the value of such object was set to true. That is, being set to false, or not being set yet, I considered the same case: not being set to true. Mixed comparison was just the right thing for the job: if (ob == true). It didn't look ambiguous, and I would certainly never think it means "if ob is initialized" because one uses boost::none for this. But perhaps this intuition varies with individuals.

boost::tribool also provides unintuitive behaviour because !(t == true) is not same as (t == false). And similarly:
if (t) yes(); else no(); // is not same as
if (!t) no(); else yes();

Regards,
&rzej

Bjorn Reese

unread,
Sep 4, 2013, 10:07:13 AM9/4/13
to std-pr...@isocpp.org
On 09/04/2013 03:36 PM, Andrzej Krzemie�ski wrote:

> As a side note, but in response to the original question: recently, I
> had to use *boost::optional<bool>* to indicate a bool that has a
> determinate value which has not yet been computed/assigned.
> *boost::tribool* was not the right abstraction, because logical
> operators have inadequate semantics for my usage. At some point I wanted

FYI, faced with the same problem, I wrote a simple tribool where you can
use traits to determine the results of logical operators:

https://github.com/breese/tribool

Reply all
Reply to author
Forward
0 new messages