c++constantspointer-to-memberconst-correctnesssafe-bool-idiom

const-correctness and the safe bool idiom


I have another question related to the safe bool idiom:

typedef void (Testable::*bool_type)() const;             // const necessary?
void this_type_does_not_support_comparisons() const {}   // const necessary?

operator bool_type() const
{
    return ok_ ? &Testable::this_type_does_not_support_comparisons : 0;
}

How come the bool_type (the typedef) and this_type_does_not_support_comparisons are const? Nobody is supposed to actually call the member function through the return pointer anyway, right? Is const necessary here? Would operator bool_type (the member function) violate const-correctness otherwise?


Solution

  • The "safe bool idiom" is the technical answer to the question "i want a vehicle that is both sports car and tractor, and maybe a boat". The practical answer is not the technical answer…

    That said, the problem it solves is just to give a result that is convertible to bool but not to much of anything else (otherwise an instance of the class could be passed as actual argument where e.g. the formal argument was int, say). A data pointer could be convertible to void*. A function pointer isn't, at least formally within the C++ standard (Posix is something else, practice also).

    Using a member function pointer protects against accidentally calling the function, given the pointer from the safe bool operator. The const constricts it a bit, but if fate has put someone on the path of making maximum number of silly mistakes, that person might still manage to call the do-nothing function. Instead of the const I think I would just let it have an argument of a private type, where other code cannot provide such an argument, and then it doesn't have to be a silly member function type anymore.

    Can look like this:

    #include <stdio.h>
    
    class Foo
    {
    private:
        enum PrivateArg {};
        typedef void (*SafeBool)( PrivateArg );
        static void safeTrue( PrivateArg ) {}
    
        bool    state_;
    
    public:
        Foo( bool state ): state_( state ) {}
    
        operator SafeBool () const
        { return (state_? &safeTrue : 0); }
    };
    
    int main()
    {
        if( Foo( true ) ) { printf( "true\n" ); }
        if( Foo( false ) ) { printf( "false\n" ); } // No output.
    
        //int const x1 = Foo( false );        // No compilado!
        //void* const x2 = Foo( false );      // No compilado!
    }
    

    Of course, the practical answer is instead something like this:

    #include <stdio.h>
    
    class Foo
    {
    private:
        bool    isEmpty_;
    
    public:
        Foo( bool asInitiallyEmpty )
            : isEmpty_( asInitiallyEmpty )
        {}
    
        bool isEmpty() const { return isEmpty_; }
    };
    
    int main()
    {
        if( Foo( true ).isEmpty() ) { printf( "true\n" ); }
        if( Foo( false ).isEmpty() ) { printf( "false\n" ); } // No output.
    
        //bool const x0 = Foo( false );       // No compilado!
        //int const x1 = Foo( false );        // No compilado!
        //void* const x2 = Foo( false );      // No compilado!
    }
    

    Summary wrt. questions asked:

    • How come the bool_type (the typedef) and this_type_does_not_support_comparisons are const?

    Somebody didn't quite understand what they coded. Or maybe they intended to restrict the ability to call, a little. But then, pretty futile measure.

    • Nobody is supposed to actually call the member function through the return pointer anyway, right?

    Right.

    • Is const necessary here?

    No.

    • Would operator bool_type (the member function) violate const-correctness otherwise?

    No.

    Cheers & hth.,