This pattern shows a way to create a type that can be bound, at run-time, to an object of any type that supports a given interface, and then used to invoke operations on the object, regardless of its type. The object that is bound to could be a built-in type, or any user-defined type with the required interface. This allows the flexibility of a template, but at run-time.
For example, we could create a type that could be bound to any object that supported assignment:
void user( assignable_ref<int> i ) {
i = 1234;
}
class Foo {
public:
void operator = ( int );
};
void supplier() {
int i;
use( i );
Foo foo;
use( foo );
}
assignable_ref
acts like a reference to an object of any type that supports assignment.
template<typename T>
class assignable_ref {
public:
assignable_ref( assignable_ref const& );
template<typename U>
assignable_ref( U& );
void operator = ( T const& );
};
The implementation involves keeping a reference to the referred-to object and some sort of information on how to invoke the assignment. The latter could be a function pointer or pointer to a type with a virtual invoke function. This invocation information is generated in the constructor template function.
Here is an implementation that uses a function pointer. This makes it easy to copy and doesn't require any freestore.
template<typename T>
class assignable_ref {
void* const obj;
void (* const assign_func)( T const& );
template<typename U>
static void assign( void* obj, T const& value ) {
*static_cast<U*> (obj) = value;
}
public:
// default OK
// assignable_ref( assignable_ref const& );
template<typename U>
assignable_ref( U& u ) : obj( &u ), assign_func( assign<U> ) {
}
void operator = ( T const& value ) {
assign_func( obj, value );
}
};
It's quite simple, but has lots of power. The fundamental idea is to capture static information when it's available (in the constructor) and convert it to dynamic information (data) that can be used at run-time to recover the static information.
Here is another instance of this pattern. This type has value semantics (it can be copied and assigned). It can hold a value of any type that has a member function do_something()
.
class has_do_something {
struct base {
virtual ~base() { }
virtual base* clone() const = 0;
virtual void do_something() = 0;
};
template<typename T>
struct object : base
{
T obj;
object( T const& o ) : obj( o ) { }
virtual base* clone() const {
return new object( this->obj );
}
virtual void do_something() {
obj.do_something();
}
};
struct null_object : base
{
virtual base* clone() const {
return new null_object;
}
virtual void do_something() {
throw "null object"
}
};
std::auto_ptr<base> obj;
public:
has_do_something() : obj( new null_object ) { }
has_do_something( has_do_something const& from ) : obj( from.obj->clone() ) {
}
has_do_something& operator = ( has_do_something const& from ) {
obj.reset( from.obj->clone() );
return *this;
}
template<typename U>
has_do_something( U const& u ) : obj( new object<U>( u ) ) {
}
void do_something() {
obj->do_something();
}
};
Here, we see that this pattern can look more like a managed interface object created on-the-fly. We can slide more towards that and away from this pattern, for illustrative purposes:
struct do_something_base {
virtual void do_something() const = 0;
};
void user( do_something_base const& obj ) {
obj.do_something();
}
template<class T>
class do_something_t : public do_something_base {
T* const obj;
public:
do_something_t( T* p ) : obj( p ) {
}
virtual void do_something() const {
obj->do_something();
}
};
struct Foo {
void do_something();
};
struct Bar {
virtual void do_something() = 0;
};
void supplier( Foo* foo, Bar* bar ) {
user( do_something_t<Foo>( foo ) );
user( do_something_t<Bar>( bar ) );
}
This shows the binding mechanism in a more raw, manual form.
:-)