views:

48

answers:

3

Is there a trick to get the safe bool idiom completely working without having to derive from a class that does the actual implementation?

With 'completely working', I mean the class having an operator allowing it to be tested as if it were a boolean but in a safe way:

MyTestableClass a;
MyOtherTestableClass b;
  //this has to work
if( a );
if( b );
  //this must not compile
if( a == b );
if( a < b );
int i = a;
i += b;

when using this implementation for example

struct safe_bool_thing
{
  int b;
};
typedef int safe_bool_thing::* bool_type;
safe_bool( const bool b ) { return b ? &safe_bool_thing::b : 0; }

class MyTestableClass
{
  operator bool_type () const { return safe_bool( someCondition ); }
}

it's almost fine, except a == b will still compile, since member pointers can be compared. The same implementation as above, but with pointer to member function instead of pointer to member variable has exactly the same problem.

Known implementations that do work perfectly (as described here for example, or the safe_bool used in boost) require that the testable class derive from a class providing the actual operator implementation.

I actually think there is no way around it, but'm not entirely sure. I tried something that looked a bit fischy but I thought it might work, yet is doesn't compile at all. Why is the compiler not allowed to see that the operator returns a safe_bool_thing, which in turn can be converted to bool() and hence be tested?

struct safe_bool_thing
{
  explicit safe_bool_thing( const bool bVal ) : b( bVal ) {}
  operator bool () const { return b; }
private:
  const bool b;
  safe_bool_thing& operator = ( const safe_bool_thing& );
  bool operator == ( const safe_bool_thing& );
  bool operator != ( const safe_bool_thing& );
};

class MyTestableClass
{
  operator safe_bool_thing () const { return safe_bool_thing( someCondition ); }
};

MyTestableClass a;
if( a ); //conditional expression of type 'MyTestableClass' is illegal
A: 

EDIT: Oh! I did not read your requirements correctly, so the below does not satisfy all of them.

It is easy enough without any special base class:

struct MyClass
{
   int some_function () const;

   typedef int (MyClass:: * unspec_bool_type) () const;
   operator unspec_bool_type () const
   {
       return some_condition ? &MyClass::some_function : 0;
   }
};

So, given suitable member variable of member function, you can impement it in just 5 lines of simple code.

wilx
yes this solution is basically what I meant with 'The same implementation as above, but with pointer to member function instead of pointer to member variable'..
stijn
+1  A: 

"Why is the compiler not allowed to see"

I don't have an answer for safe bool, but I can do this bit. It's because a conversion sequence can include at most 1 user-defined conversion (13.3.3.1.2).

As for why that is - I think someone decided that it would be too hard to figure out implicit conversions if they could have arbitrarily many user-defined conversions. The difficulty it introduces is that you can't write a class with a conversion, that "behaves like built-in types". If you write a class which, used idiomatically, "spends" the one user-defined conversion, then users of that class don't have it to "spend".

Not that you could exactly match the conversion behaviour of built-in types anyway, since in general there's no way to specify the rank of your conversion to match the rank of the conversion of the type you're imitating.

Edit: A slight modification of your first version:

#define someCondition true

struct safe_bool_thing
{
    int b;
};
typedef int safe_bool_thing::* bool_type;
bool_type safe_bool( const bool b ) { return b ? &safe_bool_thing::b : 0; }

class MyTestableClass
{
public:
    operator bool_type () const { return safe_bool( someCondition ); }
private:
    bool operator==(const MyTestableClass &rhs);
    bool operator!=(const MyTestableClass &rhs);
};

int main() {
    MyTestableClass a;
    MyTestableClass b;
    a == b;
}

a == b will not compile, because function overload resolution ignores accessibility. Accessibility is only tested once the correct function is chosen. In this case the correct function is MyTestableClass::operator==(const MyTestableClass &), which is private.

Inside the class, a == b should compile but not link.

I'm not sure if == and != are all the operators you need to overload, though, is there anything else you can do with a pointer to data member? This could get bloated. It's no better really than Bart's answer, I just mention it because your first attempt was close to working.

Steve Jessop
good point here.
stijn
stijn
+2  A: 

This should work:

class MyTestableClass
{
private:
  void non_comparable_type() {}
public:
  typedef void (MyTestableClass::* bool_type)();

  operator bool_type () const { return (someCondition ? &MyTestableClass::non_comparable_type : 0); }
};

class MyOtherTestableClass
{
private:
  void non_comparable_type() {}
public:
  typedef void (MyOtherTestableClass::* bool_type)();

  operator bool_type () const { return (someCondition ? &MyOtherTestableClass::non_comparable_type : 0); }
};

For blocking the if (a == b) case, it depends on the fact that both types convert to incompatible pointer types.

Bart van Ingen Schenau
ha! this one does work indeed. Just has the disadvantage of not being entirely reusable but that's what you get when not deriving I guess.
stijn
Yes. The main reason for inheritance here is re-use of the implementation.
Bart van Ingen Schenau