Safely call a member function through a possibly null pointer preserving argument side effects

Normally if we have a pointer to an object and want to call a member function we will guard the call like this:

if ( ptr ) ptr->member();

If we need to use the result in an expression we need to do this:

ptr ? ptr->member() : alternative

More complex is a case where we need to pass an argument which has a side effect:

if ( ptr ) ptr->member( ++it ); else ++it;

Here we must duplicate code for each argument which has a side effect. For an expression this is much more complex.

safe_call allows us to replace these with the following:

safe_call( ptr, &MyClass::member )( ++it )

This form works as both a statement and an expression. If ptr is null then a default constructed item of the correct return type of MyClass::member is returned.

Where the return type is not default constructable or a different alternative value is needed there is also a three argument version of safe_call.

safe_call( ptr, &MyClass::member, alternative )( ++it )

If the pointer is null then the alternative value is returned.

Notes

  • Temporaries are not created, i.e. in the example usage “world 2” is not coerced into an object of type foo because the function call does not happen.
  • To remove a number of complications due to object lifetimes safe_call will not work with member functions that return references.
  • The maximum number of arguments is controlled through SAFECALL_MAX_PARAMS.
By Kirit with contributions by nobody yet.

Recipe source code

#include <boost/type_traits.hpp>
#include <boost/preprocessor/arithmetic/inc.hpp>
#include <boost/preprocessor/punctuation/comma_if.hpp>
#include <boost/preprocessor/repetition.hpp>

namespace boost {
    namespace detail {
        template<typename R, typename C>
        struct function_traits_helper< R (C::**)(void) const >
        {
          BOOST_STATIC_CONSTANT(int, arity = 0);
          typedef R result_type;
          typedef C class_type;
        };
        template<typename R, typename C, typename A1>
        struct function_traits_helper< R (C::**)( A1 ) const >
        {
          BOOST_STATIC_CONSTANT(int, arity = 1);
          typedef R result_type;
          typedef C class_type;
          typedef A1 arg1_type;
        };
        template<typename R, typename C, typename A1, typename A2 >
        struct function_traits_helper< R (C::**)( A1, A2 ) const >
        {
          BOOST_STATIC_CONSTANT(int, arity = 2);
          typedef R result_type;
          typedef C class_type;
          typedef A1 arg1_type;
          typedef A2 arg2_type;
        };
    }
}


/*
    safe_call
*/

#ifndef SAFECALL_MAX_PARAMS
    #define SAFECALL_MAX_PARAMS 16
#endif

#define SAFECALL_PARAM_DECL(z, n, _) BOOST_PP_COMMA_IF(n) A ##  n a ## n
#define SAFECALL_FORWARDER( z, n, t ) \
    template< BOOST_PP_ENUM_PARAMS( n, typename A ) > \
    result_type operator()( BOOST_PP_REPEAT( n, SAFECALL_PARAM_DECL, _) ) const { \
        if ( m_self ) return (m_self->*m_member)( BOOST_PP_ENUM_PARAMS( n, a ) ); \
        else return t; \
    }

template< typename F, bool > class safe_call_operator;

template< typename F >
class safe_call_operator< F, false > {
protected:
    typedef typename boost::function_traits< F >::result_type result_type;
    BOOST_STATIC_ASSERT( ! boost::is_reference< result_type >::value );
    typedef typename boost::function_traits< F >::class_type class_type;
    class_type *m_self;
    F m_member;
public:
    safe_call_operator< F, false >( class_type *s, F m )
    : m_self( s ), m_member( m ) {
    }

    result_type operator()() const {
        if ( m_self ) return (m_self->*m_member )();
        else return result_type();
    }
    BOOST_PP_REPEAT_FROM_TO( 1, BOOST_PP_INC( SAFECALL_MAX_PARAMS ), SAFECALL_FORWARDER, result_type() )
};

template< typename F >
class safe_call_operator< F, true > : private safe_call_operator< F, false > {
    result_type m_return;
public:
    safe_call_operator< F, true >( class_type *s, F m, result_type d )
    : safe_call_operator< F, false >( s, m ), m_return( d ) {
    }

    result_type operator()() const {
        if ( m_self ) return (m_self->*m_member )();
        else return m_return;
    }
    BOOST_PP_REPEAT_FROM_TO( 1, BOOST_PP_INC( SAFECALL_MAX_PARAMS ), SAFECALL_FORWARDER, m_return )
};

#undef SAFECALL_FORWARDER
#undef SAFECALL_PARAM_DECL_REF
#undef SAFECALL_PARAM_DECL
#undef SAFECALL_MAX_PARAMS

template< typename F > inline
safe_call_operator< F, false > safe_call( typename boost::function_traits< F >::class_type *s, F f ) {
    return safe_call_operator< F, false >( s, f );
}
template< typename F, typename D > inline
safe_call_operator< F, true > safe_call( typename boost::function_traits< F >::class_type *s, F f, D d ) {
    return safe_call_operator< F, true >( s, f, typename boost::function_traits< F >::result_type( d ) );
}


/*
    Test on a foo
*/

#include <string>
#include <iostream>


struct foo {
    foo( const char *str )
    : m_str( str ) {
        std::cout << "Constructed with: " << m_str << std::endl;
    }
    std::string bar() const {
        return m_str;
    }
    std::string baz( const foo &p ) const {
        return m_str + " " + p.m_str;
    }
    size_t boz( int i ) const {
        return i + m_str.length();
    }
    size_t biz( int l, int r ) const {
        return l * m_str.length() + r;
    }
    const std::string &my_str() const {
        return m_str;
    }
private:
    const std::string m_str;
};


int main() {
    foo
            instance( "Hello" ),
            &instance_ref( instance ),
            *pointer_instance = &instance,
            *pointer_null = 0;

    int calls = 0;

    std::cout
            << "Instance:\n"
            << std::string( safe_call( pointer_instance, &foo::bar )() ) << "\n"
            << safe_call( pointer_instance, &foo::bar )() << "\n"
            << safe_call( pointer_instance, &foo::baz, "bypassed" )( "world 1" ) << "\n"
            << safe_call( pointer_instance, &foo::boz )( ++calls ) << "\n"
            << safe_call( pointer_instance, &foo::biz )( 3, 5 ) << "\n"
            << std::endl;

    std::cout
            << "Null foo:\n"
            << std::string( safe_call( pointer_null, &foo::baz )( instance_ref ) ) << "\n"
            << safe_call( pointer_null, &foo::bar )() << "\n"
            << safe_call( pointer_null, &foo::baz, "bypassed" )( "world 2" ) << "\n"
            << safe_call( pointer_null, &foo::boz )( ++calls ) << "\n"
            << std::endl;

    std::cout << "Calls should be 2: " << calls << std::endl;

    // Neither of these will compile because my_str returns a reference
    //std::cout << safe_call( pointer_instance, &foo::my_str )() << std::endl;
    //std::cout << safe_call( pointer_null, &foo::my_str )() << std::endl;

    return 0;
}

This is not an official Boost site. For more information on Boost please see Boost.org.