You can do something like this, but because C++ lacks reflection capabilities you have to do some extra work to make it possible.
struct base {
virtual void call_method( std::string const & ) = 0;
};
struct derived : public base {
std::string foo( ) const {
return "bar";
}
// More methods.
void call_method( std::string const &p_name ) {
if( p_name == "foo" ) {
this -> foo( );
}
// More checks on method names.
else {
// Handle invalid function name.
}
}
};
This is called a data-driven interface, where you pass commands to objects and they respond to the commands that they recognize in a polymorphic fashion. You can improve on what I showed by creating a statically initialized unordered map from commands to function pointer and then using that to resolve which function to call. It's good to avoid this type of function dispatch if you can, though, because it's slow in comparison to static function dispatch and error prone since typos may result incorrect calls or errors. It also has the downside that you can't get the return value easily, though it is possible in some cases.
EDIT: I wanted to give a more complete example of how this can be done, so here goes:
#include <cassert>
#include <iostream>
#include <boost/bind.hpp>
#include <boost/blank.hpp>
#include <boost/variant.hpp>
#include <boost/function.hpp>
#include <boost/unordered_map.hpp>
#include <boost/assign/list_of.hpp>
// A base class that defines an interface to call methods by name
// and to access the list of methods. We use a map of argument
// names to boost::variants to pass arguments to the functions.
// Right now we support only ints and strings, but we can expand
// this to other types if we want. In particular, we can use
// boost::any to support arbitrary types, but it will be slow.
// Maybe that's not a big deal since function dispatch through
// named functions is slow anyway.
struct base {
typedef boost::variant< boost::blank, int, std::string > argument_t;
typedef boost::variant< boost::blank, int, std::string > return_t;
typedef boost::unordered_map< std::string, argument_t > param_map_t;
typedef boost::function< return_t ( base *, param_map_t const & ) >
method_t;
typedef boost::unordered_map< std::string, method_t > method_map_t;
return_t call_method(
std::string const &p_method
, param_map_t const &p_params = param_map_t( )
)
{
method_map_t::const_iterator l_itr =
get_methods( ).find( p_method );
if( l_itr == get_methods( ).end( )) {
// Handle undefined method identifier.
}
return l_itr -> second( this, p_params );
}
virtual method_map_t const &get_methods( ) const = 0;
};
// A trampoline object to elide the concrete type that
// implements the base interface and to provide appropriate
// casting. This is necessary to force all functions in our
// method map to have the same type.
template< typename U >
base::return_t trampoline(
base::return_t (U::*p_fun)( base::param_map_t const & )
, base *p_obj
, base::param_map_t const &p_param_map
)
{
U *l_obj = static_cast< U* >( p_obj );
return (l_obj ->* p_fun)( p_param_map );
}
// A derived type that implements the base interface and
// provides a couple functions that we can call by name.
struct derived : public base {
static method_map_t const c_method_map;
return_t foo( param_map_t const &p_params ) {
std::cout << "foo" << std::endl; return 1;
}
return_t bar( param_map_t const &p_params ) {
std::cout << "bar" << std::endl; return std::string( "bar" );
}
method_map_t const &get_methods( ) const {
return c_method_map;
}
};
// Construct map of method names to method pointers for derived.
base::method_map_t const derived::c_method_map = boost::assign::map_list_of
( "foo", boost::bind( &trampoline< derived >, &derived::foo, _1, _2 ))
( "bar", boost::bind( &trampoline< derived >, &derived::bar, _1, _2 ))
;
int main( ) {
base *blah = new derived( );
// Call methods by name and extract return values.
assert( boost::get< int >( blah -> call_method( "foo" )) == 1 );
assert( boost::get< std::string >( blah -> call_method( "bar" )) == "bar" );
// Iterate over available methods
typedef base::method_map_t::const_iterator iterator;
iterator l_itr = blah -> get_methods( ).begin( );
iterator l_end = blah -> get_methods( ).end ( );
for( ; l_itr != l_end; ++l_itr ) {
if( l_itr -> first == "foo" ) l_itr -> second( blah, base::param_map_t( ));
}
}
The output is:
foo
bar
foo
As you can see it's quite a bit of work to set this up, but adding new types that implement the interface is pretty easy.
#define method footo make this work. Macros are part of the preprocessor, which runs before the compiler is even started.