Sunday 28 July 2013

Contextually converted to bool

Something I found mildly surprising about C++11 is that this works:
#include <iostream>

struct Testable
{
    explicit operator bool() const { return true; }
};

int main()
{
    Testable t;
    if (t)
        std::cout << "Converted to true!\n";
}
That is, it compiles and prints Converted to true!.
The new bit here is the explicit keyword. When I first saw an example like this, I expected to have to write
if (bool( t )) // ...
If this was an operator someOtherType(), we would have to write the conversion explicitly. If we wanted to use t say as an argument to a function accepting bool, we would also have to write the conversion explicitly (it is the explicit keyword, after all!).
What makes this work is this wording in The Standard, at Section 4 Standard conversions, paragraph 3:
An expression e can be implicitly converted to a type T if and only if the declaration T t=e; is well-formed, for some invented temporary variable t (8.5). Certain language constructs require that an expression be converted to a Boolean value. An expression e appearing in such a context is said to be contextually converted to bool and is well-formed if and only if the declaration bool t(e); is well-formed, for some invented temporary variable t (8.5). The effect of either implicit conversion is the same as performing the declaration and initialization and then using the temporary variable as the result of the conversion.
So basically, in any kind of logical condition, an explicit operator bool() can be called without having to write bool(...). This replaces the need for workarounds such as operator void*, or The Safe bool Idiom (if you were wondering why you wouldn't just use operator bool() without the explicit, that link will explain it).

I've been trying to enumerate all the cases in The Standard where it uses the wording "contextually converted to bool", but I may have missed some:
  • Negation: !t (5.3.1 p9).
  • Logical AND: t&&s (5.14).
  • Logical OR: t||s (5.15).
  • Conditional operator: t?"yup":"nope" (5.16 p1).
  • Selection statement (other than switch): if (t) or if (Testable t{}) (6.4 p4).
  • for statement, for(;t;) //..., and
  • while statement, while(t) //.... The wording isn't used directly for these, and they are actually defined in section 6.5, but 6.4 p2 says "The rules for conditions apply both to selection-statements and to the for and while statements (6.5)."
  • do statement: do {//...} while (t); (6.5.2 p1).
  • static-assert_declaration: static_assert(t); (note you will need constexpr here) (7 p4).
  • Exception specification: SomeType foo() noexcept(t); (note you will need constexpr here) (15.4 p1).
  • NullablePointer concept: Any type P that can be used where the standard library requires a type fulfilling the NullablePointer concept, it is required that an instance of P can be contextually converted to bool (17.6.3.3 p3).
  • Any algorithm in the <algorithm> header that takes a template parameter named Predicate, then for an instance pred of that type, it must support pred(*first) being contextually converted to type bool. (25.1 p8)
    Similarly, for a BinaryPredicate binary_pred, it is required that binary_pred(*first1, *first2) or binary_pred(*first1, value) can be contextually converted to type bool. (25.1 p9)
  • For any algorithm taking a comparator type Compare, for an instance Compare comp, the return value when contextually converted to type bool must convert to true if the first argument is less than the second, and false otherwise. (25.4 p2)
And I think that's it! This is being made use of in various places throughout the c++11 Standard, for instance in the basic_ios template, operator void*() has been replaced with explicit operator bool(). It is also used to simulate checking for NULL in the Standard smart pointers, shared_ptr and unique_ptr. Have a look through these.

Note: I don't have the official ISO C++ Standard published 2011-09-01. I am looking at N3337, the first draft published after the official Standard, on 2012-16-01. In N3337, the wording for Logical AND is contextually converted to type bool. This is corrected in the most recent c++14 draft N3691 (2013-05-16).

Let me make it entirely clear that the previous methods of achieving this behaviour (e.g. operator void*()) still work as ever. It's just there is now a nicer way (I consider it nicer in that it more clearly indicates intent, and actually does what you wanted).

3 comments:

  1. Hi Chris,
    I organise a C++ Meet up in London would you be interested in sharing your knowledge by giving a talk?

    ReplyDelete
    Replies
    1. Hi Jason, I'll take that as a compliment (or you're getting desperate :) ), but I don't think I'm at that sort of level yet.
      I do work pretty close by though, and some of my colleagues have been and said good things, so I may come along to the next one and watch (the problem is, it takes me at least an hour to get home afterwards).
      For anyone else interested: event link.

      Delete