views:

176

answers:

5

I've recently been doing a huge refactoring where I was changing a lot of my code to return booleans instead of an explicit return code. To aid this refactoring I decided to lean on the compiler where possible by getting it to tell me the places where my code needed to be changed. I did this by introducing the following class (see here for the lowdown on how this works):

///
/// Typesafe boolean class
///
class TypesafeBool
{
private:
    bool m_bValue;
    struct Bool_ { 
        int m_nValue; 
    };
    typedef int Bool_::* bool_;
    inline bool_ True() const { return &Bool_::m_nValue; }
    inline bool_ False() const { return 0; }

public:
    TypesafeBool( const bool bValue ) : m_bValue( bValue ){}
    operator bool_() const { return m_bValue ? True() : False(); }
};

Now, instead of using a normal bool type as the return type, I used this class which meant that I couldn't compile something like this any more:

TypesafeBool SomeFunction();
long result = SomeFunction(); // error

Great: it has made the refactoring manageable on a huge codebase by letting the compiler do a lot of the hard work for me. So now I've finished my refactoring and I'd quite like to keep this class hanging around and carry on using it since it affords us an extra level of safety that the built-in bool type doesn't.

There is however one "problem" which is preventing me from doing this. At the moment we make heavy use of the ternary operator in our code, and the problem is that it is not compatible with this new class without explicit casts:

TypesafeBool result = ( 1 == 2 ? SomeFunction() : false ); // error: different types used
TypesafeBool result = ( 1 == 2 ? SomeFunction() : (TypesafeBool)false );

If I could "solve" this issue so that I could use my class in a seamless manner I would probably carry on using it throughout the codebase. Does anyone know of a solution to this issue? Or is it just impossible to do what I want?

+1  A: 

I don't know about a seamless manner, the ternary operator has some restrictions on its use...

However, why don't you define two constants ?

TypesafeBool const True = TypesafeBool(true);
TypesafeBool const False = TypesafeBool(false);

And then:

TypesafeBool result = ( 1 == 2 ? SomeFunction() : False );

Of course, it's a bit unorthodox since I play on the capitalization to avoid reusing a reserved word :)

Matthieu M.
The problem with this is interop with code that uses `bool`. I had a go at something like this already but found that this was the stumbling block.
jkp
you could interop by adding an implicit cast to bool, but beware, this cure may be worse than the disease
jk
@jk: implicit cast? Can you give me an example of what you mean?
jkp
No for implicit casts! `operator bool` made me very sad a while back.
Dominic Rodger
-1, claimed restriction (exact type match) is patently untrue. One of the two may even be a void throw expression!
MSalters
@MSalters - In fact, `BOOST_FOREACH` works because the `?:` operator does not require exact types! (http://www.artima.com/cppsource/foreach2.html)
In silico
@Dominic, could you tell us an example ? examples may be striking in order to convince. I just know by heart that it may let some unwanted code accepted because legal C++ when converted to bool, but I would like an example of the damage it can do.
Stephane Rolland
@Stephane - what would this code (http://codepad.org/bwdHV9e1) output? Why? It was code very, very similar to this that made me spend a whole day in confusion. There were, alas, no actual gnomes in my code.
Dominic Rodger
@Dominic, okay I see better, however I would say there is also a bug also in the function operator bool(void) const { return m_status == NO_GNOMES_IN_PIPELINE; } because I feel like it should have been operator bool(void) const { return m_status == GNOMES_IN_PIPELINE; }... it's just a detail...
Stephane Rolland
@Stephane - huh? Are you saying you want the gnomes to die? ++surrealism.
Dominic Rodger
@Dominic oh no I couldn't have said that, it's you! you having armed fire weapons. Personnally, I am friendly with elves.
Stephane Rolland
@MSalters: sorry, I can't seem to phrase it correctly. Nonetheless, as the OP has proved, trying to return either `TypesafeBool` or `bool` when the former can be implicitly from the latter still doesn't work so there is indeed a restriction on the type. Do you happen to know the exact wording ?
Matthieu M.
The actual "restrictions" are basically a long list describing what the type of the expression is, given the types of the operands. If a combination of types is not on that list, it's illegal. Probably the most important rule is that if one operand has _class_ type, the other must also have the _same class_ type - but you're wrong when you generalize this to non-class types such as pointers. E.g. `true ? new float : 0` is legal even though `0` is not a `float*`.
MSalters
@MSalters: okay, I didn't considered the built-ins case (and indeed didn't know much about them in this case). I would say that in C++0x the correct form would be `true ? new float : nullptr`, since `0` here is basically a stand-in for the null pointer (It could stand for an `int` too).
Matthieu M.
+1  A: 
      class CCastableToBool
      {  
      public:
         // ...
         operator bool() const
         {  
           //...
           {
              return true;
           } 
           //...
           return false;
         }  
      private:
         // ...
      }; 

but beware, in C++ it is considered really dangerous to have a class that can be casted to bool. You are warned :-)

you can read this there, SafeBool

You should explicitely call TypesafeBool::True() in all your ternary tests.

TypesafeBool result = ( 1 == 2 ? SomeFunction().True() : false );
Stephane Rolland
Still won't work in a ternary operation in my tests.
jkp
@Stephane: yeah, found that article just now....interesting read.
jkp
@jkp when you say it won't work, you mean it doesnt compile ? could you paste the compilation error ? because I have already coded cast operators and they have all worked, so I am a bit astonished.
Stephane Rolland
@jkp ;-) no problem, but I insist, I warn you, it is carved in my mind that implicit cast to bool is dangerous in C++.
Stephane Rolland
@Stephane: weird: it won't compile if you add a constructor with a parameter to that class....can't for the life of me see why: http://codepad.org/N1Eeohh4. If you comment out the constructor taking long, it works. Any ideas?
jkp
@jkp could you copy/paste the compilation error ? Constructors are used by the compilator to do convertion on the fly. One thing you could try is to declare the long constructor explicit: class CCastableToBool{ public: CCastableToBool() {} explicit CCastableToBool( long ) {}//...}this way it won't try to use it unless you explicitely use it this way: CCastableToBool bTest(50);
Stephane Rolland
@Stephane: It seems like a regression to me, `jkp` has a nice safe bool idiom and your proposition means going back to an unsafe one.
Matthieu M.
@Matthieu I answer to the question "how to cast a class to bool". I answer this because he obviously doesnt have the knowledge about cast operators, otherwise he would not have asked. Now he surely knows he can define operator Anytype(). But if you read well: I warn him it is dangerous he should do so in C++ with a class casting to bool. And I even included a link that explain why cf: SafeBool.
Stephane Rolland
@Matthieu, that's right, I have added a comment to explictely say it would be better otherwise.
Stephane Rolland
@Stephane: but if you look closely he is already using something close to SafeBool (pointer to member instead of pointer to member function). Therefore there is no issue with his code.
Matthieu M.
@Matthieu, Alright. I prefer calling TypesafeBool::True(); I'm gonna correct my answer.
Stephane Rolland
+2  A: 

In the context of the conditional operator, the type of the expression is the common type of the last two operands. The complete rules to determine this common type are a bit complex, but your case happens to be trivial: if one of the two possible return values is a class type, the other value must have the same class and the common type is obviously also that class.

That means that if one of the operands is a TypesafeBool, then the other must be as well.

Now the problem you're really trying to solve has been solved before. The trick is not providing a class; instead use a typedef. See for instance safe bool.

MSalters
@MSalters: What I really want is something I can turn on for debug builds and off otherwise: ideally I'd #define whatever this typesafe class is to `bool` and it would be interchangeable with real `bool` instances so I could get this extra safety when in development. Can you show how this can be done?
jkp
@jkp: Typically you'd do that by `#ifdef NDEBUG typedef bool BOOL; #else typedef boost::safe_bool BOOL #endif`.
MSalters
A: 

Is it a possibility to make the constructor of TypesafeBool explicit? Of course, now the usage has to be

TypesafeBool result( 1 == 2 ? b : false );
Chubsdad
@chubsdad: its chicken and egg it seems. I need the constructor to be explicit so that the compiler can work out what the types on either side should resolve to unambiguously but that in turn means I can't implicitly assign `bool` instances to `TypesafeBool` instances. Shame, I don't think a drop-in replacement is possible.
jkp
A: 

Could you use an assignment operator that takes in a bool as the external argument, as well as one that takes a TypesafeBool? It might be something to try out...

Shotgun Ninja