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?
-
The answer to your first question is "yes", but your "constructor call" is wrong.
A adeclares 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 }Pavel Minaev : Actually, the answer to the second question is "yes": the following is perfectly legal C++: `if (A a = A()) { /* use a */ }`Steve Jessop : Good point, well made. I'll edit. -
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 andvoid*, 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_castthough, but can be perfectly used together with a user defined type like above, tooif(Derived *derived = dynamic_cast<Derived*>(base)) { // do stuff }Note that syntactically, you have to initialize the variable (using the
= expressionform like for a default argument). The following is not validif(ifstream ifs("file.txt")) { // invalid. Syntactic error }Pavel Minaev : 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.Johannes Schaub - litb : Yeah, right. I will clarify :) That said, something like `for(A a(123); ; );` is perfectly valid though unless you meant `for(; A a(123); );` :)Pavel Minaev : 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).mLewisLogic : litb - Thanks for the clarification. Didn't realize variable initialization was required. -
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
Awould have a constructor such asA(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 thatboolis in turn implicitly convertible to any numeric type (withfalse == 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
boolconversion (as usual, null pointer isfalse, 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 (autoanddecltypein C++0x provide a way, though).As a side note,
main()as written is not valid C++ - ISO C++ does not have "defaultint" rule the way C does, and a function without an explicit return type is invalid.Steve Jessop : "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.Johannes Schaub - litb : Uh, while i was enhancing my answer, you came up with safe-bool too. Haha +1 for a good answer tho xDJohannes Schaub - litb : 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.Pavel Minaev : Good point; I checked it on VC10, which seems to have this among other C++0x extensions. I'll edit this back. -
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; } }mLewisLogic : 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.GMan : Ah :( [15chars]
0 comments:
Post a Comment