I think that any explanation without the code would be just more obscure. So here is the code where I tried to keep everything as simple as possible.
#include <vector>
#include <iostream>
class WithParametersBase
{
public:
WithParametersBase();
double getX() const {return 0.0;}
double getY() const {return 1.0;}
//let's say I want to access these members using an unified interface:
double getParameter(int index) const;
// For example index == 0 means getX and index == 1 means getY.
// I could implement it for example like this:
protected:
void addGetter(double (WithParametersBase::* getter)()const)
{
getters_.push_back(getter);
}
std::vector<double (WithParametersBase::*)()const> getters_;
};
WithParametersBase::WithParametersBase()
{
addGetter(&WithParametersBase::getX);
addGetter(&WithParametersBase::getY);
}
double WithParametersBase::getParameter(int index) const
{
return (this->*(getters_[index]))();
}
Indeed it works. With a test program:
int main(int argc, char *argv[])
{
WithParametersBase base;
std::cout << base.getParameter(0)
<< base.getParameter(1) << std::endl;
return 0;
}
The printout is correct:
01
But in case I wnat to extend this class:
class WithParametersDerived : public WithParametersBase
{
public:
WithParametersDerived();
double getZ() const {return 2.0;} // A new getter
};
WithParametersDerived::WithParametersDerived()
{
// I want to integrate the new getter into the previous interface
addGetter(&WithParametersDerived::getZ);
}
so that if I call:
WithParametersDerived derived;
std::cout << derived.getParameter(2) << std::endl;
I want to get get a
2
I cannot compile the program. I get an error:
error: no matching function for call to
'WithParametersDerived::addGetter
(double (WithParametersDerived::*)()const)'
Which is reasonable, but I do not know how else to implement it.
I want the creator of the derived class to be able to just add the new getter. I know, that it somehow doesn’t feel right doing all this at runtime, but I do not see a template solution or a preprocessor solution. If you have some suggestions, please let me know. Anything!
I’ll sidestep the why you need such a scheme, and focus on the how.
Instead of member function pointers, you can use a
std::function<double ()>, which is a generic wrapper around any callable entity having the signaturedouble foo(). To create astd::function<double ()>out of a member function and an object instance, you usestd::bindas follows:If you’re not using C++11, std::function and std::bind are also available in Boost as boost::function and boost::bind. The Boost documentation for these are mostly (if not entirely) applicable to their C++11 counterparts.
Instead of a
std::vector, you can use astd::mapto index getters by name. This may be more practical than maintaining a central list of parameter ID numbers.If your parameters can be of different type than
double, then you may want to consider using boost::any or boost::variant as the return type.Here’s a complete working example using
std::function,std::bind, andstd::map:The downside of this approach is that each instance of
WithParametersBase(or a descendant) will contain aGetterMap. If you have a large amount of such objects, the memory overhead of all thoseGetterMapsmay be undesirable.Here’s a more efficient solution that does away with
std::functionandstd::bind. Regular function pointers and static member functions are used for getter callbacks. The object instance for which a parameter is requested is passed as an argument to these static member functions. In derived types, the instance reference is first downcast to the derived type before invoking the member function that does the actual getting.There is now only one
GetterMapper class instead of per object. Note the use of the “construct on first use” idiom in thegetters()method to avoid static initialization order fiasco.The downside with this solution is that there is more boilerplate code to write for each class derived from
WithParametersBase. It might be possible to reduce the amount of boilerplate code using templates (it would definitely be possible with macros).