4

Can a constructor call be evaluated to a boolean if the bool() operator is overloaded?

class A
{
  public:
  A() {};
  operator bool() const { return true; }
}

main()
{
  if (A a = A())
  {
  // do stuff
  }
}

Is the above code valid, or do I need to implement main like:

int main(int argc, const char* argv[])
{
A a();
if (a)
  {
  // do stuff
  }
}

This code is going to wind up all over the place in my code base, so less lines, increased legibility, and reduced scope are important, and would be improved by this.

Any ideas?

Mike Lewis
  • 734
  • 1
  • 7
  • 18

4 Answers4

8

The code contains a few syntactic and semantic bugs. Let's fix them

class A
{
public:
  A() {};
  operator bool() { return true; }
};

int main()
{
  if (A a = A())
  {
    // do stuff
  }
}

You may choose to change the type in the conversion function to something else. As written, the boolean conversion will also succeed to convert to any integer type. Converting to void* will limit conversion to only bool and void*, which is a commonly used idiom. Yet another and better way is to convert to some private type, called safe bool idiom.

class A
{
private:
  struct safe_bool { int true_; };
  typedef int safe_bool::*safe_type;
public:
  A() {};
  operator safe_type() { return &safe_bool::true_; }
};

Back to syntax: If you have an else part, you may use the name of the declared variable, because it's still in scope. It's destroyed after all branches are processed successfully

if(A a = A())
{ ... }
else if(B b = a)
{ ... }

You may also use the same name as before, and the variable will shadow the other variables, but you may not declare the same name in the most outer block of any branch - it will conflict rather than hide with the other declaration.

if(int test = 0)
{ ... }
else
{ int test = 1; /* error! */ }

The technique to declare and initialize a variable is most often used together with dynamic_cast though, but can be perfectly used together with a user defined type like above, too

if(Derived *derived = dynamic_cast<Derived*>(base)) {
  // do stuff
}

Note that syntactically, you have to initialize the variable (using the = expression form like for a default argument). The following is not valid

if(ifstream ifs("file.txt")) {
  // invalid. Syntactic error
}
Johannes Schaub - litb
  • 496,577
  • 130
  • 894
  • 1,212
  • Not just "initialize" - something like `if (A a(123))` would also be invalid even in the presence of a matching constructor. Just like `for`, it requires copy-initialization form with `=` there. – Pavel Minaev Sep 04 '09 at 16:49
  • Yeah, right. I will clarify :) That said, something like `for(A a(123); ; );` is perfectly valid though unless you meant `for(; A a(123); );` :) – Johannes Schaub - litb Sep 04 '09 at 17:14
  • Heh, I didn't realize `for` actually lets you write this, but you are right there. Also, +1 for mentioning how this is most handy when `dynamic_cast` is involved (and I find it strange that so many C++ devs seem to be unaware of this idiom). – Pavel Minaev Sep 04 '09 at 17:17
  • litb - Thanks for the clarification. Didn't realize variable initialization was required. – Mike Lewis Sep 04 '09 at 17:52
  • For reference, see also: Is the safe-bool idiom obsolete in C++11? http://stackoverflow.com/questions/6242768/is-the-safe-bool-idiom-obsolete-in-c11 – augustin Oct 25 '14 at 09:01
2

The answer to your first question is "yes", but your "constructor call" is wrong. A a declares a variable. It's a statement, not an expression. A() is an expression which constructs an anonymous temporary instance of A:

struct A
{
    A() {};
    operator bool() { return true; }
};

int main()
{
    if (A())
    {
        // do stuff
    }
}

If you want to use the instance of A in "stuff", then you need:

if (A a = A()) {
    // do stuff
}
Steve Jessop
  • 273,490
  • 39
  • 460
  • 699
  • 2
    Actually, the answer to the second question is "yes": the following is perfectly legal C++: `if (A a = A()) { /* use a */ }` – Pavel Minaev Sep 04 '09 at 16:37
2

You can do this, but only when you're using the copy-initialization syntax to call your constructor. For example:

main()
{
    if (A a = A())
    {
       // do stuff
    }
}

Most compilers would elide the copy constructor in such an initializer when optimizations are enabled, but an accessible constructor is required nonetheless.

Also, naturally, if A would have a constructor such as A(int), you could also do this:

if (A a = 123) ...

Also, it is generally considered a bad idea to have operator bool() on your class for such purposes. The reason is that bool is in turn implicitly convertible to any numeric type (with false == 0 && true == 1); so, for example, the client of your class can write:

int x = A();

void foo(float y);
foo(A());

which is probably not something you want to allow. A simple trick is to use a pointer-to-member-of-private-class instead:

class A {
private:
    struct dummy_t { int dummy; };
    typedef int dummy_t::*unspecified_boolean_type;
public:
    operator unspecified_boolean_type() const {
        if (...) {
            return &dummy_t::dummy; // true
        } else {
            return 0; // false
        }
    }
};

Pointers-to-members have an implicit bool conversion (as usual, null pointer is false, anything else is true), but they are not compatible with any type but their own; and, since the inner class here is private, no client code can possibly declare a variable of that type (auto and decltype in C++0x provide a way, though).

As a side note, main() as written is not valid C++ - ISO C++ does not have "default int" rule the way C does, and a function without an explicit return type is invalid.

Pavel Minaev
  • 99,783
  • 25
  • 219
  • 289
  • "main() as written is not valid C++" - and a class definition needs a trailing semi-colon, and operator bool() doesn't have a return type, and it (like the constructor of A) is private. – Steve Jessop Sep 04 '09 at 16:56
  • 1
    Uh, while i was enhancing my answer, you came up with safe-bool too. Haha +1 for a good answer tho xD – Johannes Schaub - litb Sep 04 '09 at 17:11
  • The most recent edit is wrong, though. The binding of a const reference to an rvalue still requires a copy constructor. The implementation may decide to elide the copy, but the copy constructor must still be callable. See the following DR: http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#391 and http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#291 (that's just about clarifying wording - it's not about requiring a copy ctor or such). If you test, be sure to disable C++0x mode, because in C++0x, no copy constructor is called or required. – Johannes Schaub - litb Sep 04 '09 at 17:34
  • Good point; I checked it on VC10, which seems to have this among other C++0x extensions. I'll edit this back. – Pavel Minaev Sep 04 '09 at 18:18
0

If you're trying to indicate failure, why not throw an exception?

#include <stdexcept>

class Foo
{
public:
    Foo(void)
    {
        if (/* something bad D: */)
        {
            throw std::runtime_error("Couldn't open file, etc...");
        }
    }
}

int main(void)
{
    try
    {
        Foo f;
        // do stuff with f
    }
    catch (std::exception& e)
    {
        std::cerr << "Error: " << e.what() << std::endl;
    }
}
GManNickG
  • 494,350
  • 52
  • 494
  • 543
  • In the lovely embedded environment that I develop in, exceptions are just not a luxury we have. Templates are about the wildest thing our compiler lets us do. – Mike Lewis Sep 04 '09 at 18:13