2

I am wrapping a library which I did not write to make it more user friendly. There are a huge number of functions which are very basic so it's not ideal to have to wrap all of these when all that is really required is type conversion of the results.

A contrived example:

Say the library has a class QueryService, it has among others this method:

WeirdInt getId() const;

I'd like a standard int in my interface however, I can get an int out of WeirdInt no problem as I know how to do this. In this case lets say that WeirdInt has:

int getValue() const;

This is a very simple example, often the type conversion is more complicated and not always just a call to getValue().

There are literally hundreds of function calls that return types likes these and more are added all the time, so I'd like to try and reduce the burden on myself having to constantly add a bajillion methods every time the library does just to turn WeirdType into type.

I want to end up with a QueryServiceWrapper which has all the same functionality as QueryService, but where I've converted the types. Am I going to have to write an identically names method to wrap every method in QueryService? Or is there some magic I'm missing? There is a bit more to it as well, but not relevant to this question.

Thanks

dpwr
  • 2,503
  • 1
  • 19
  • 36
  • What is the real name of "WeirdInt"? – Lysol Jan 27 '13 at 00:42
  • Well, that's just a simplified example. A real example would be rString (I want std::string in my interface), but this doesn't really help you as you wont have the library. – dpwr Jan 27 '13 at 00:53

4 Answers4

4

The first approach I'd think is by trying with templates such that

  • you provide a standard implementation for all the wrapper types which have a trivial getValue() method
  • you specialize the template for all the others

Something like:

class WeirdInt
{
  int v;
  public:
    WeirdInt(int v) : v(v) { }
    int getValue() { return v; }
};

class ComplexInt
{
  int v;
  public:
    ComplexInt(int v) : v(v) { }
    int getValue() { return v; }
};


template<typename A, typename B>
A wrap(B type)
{
  return type.getValue();
}

template<>
int wrap(ComplexInt type)
{
  int v = type.getValue();
  return v*2;
};

int x = wrap<int, WeirdInt>(WeirdInt(5));
int y = wrap<int, ComplexInt>(ComplexInt(10));
Jack
  • 125,196
  • 27
  • 216
  • 324
  • It is of course very useful to be able to use templates to create a wrap function that I can use for all the conversions, I've actually made such a thing already. Sorry I didn't mention this before. My problem is mainly how to write QueryServiceWrapper so that I don't have to implement all it's methods calling the wrap function in each for each method in ServiceWrapper. The more I think about it, the more I think it may not be possible. – dpwr Jan 27 '13 at 01:04
  • 1
    @dpwrussell unfortunately it is not possible in C++ (at least it has no _reflection_) -- to "copy" interface of some class, you have to redefine it in your wrapper, and delegate calls to wrapped instance.... – zaufi Jan 27 '13 at 01:15
1

If the wrapper methods for QueryService have a simple pattern, you could also think of generating QueryServiceWrapper with some perl or python script, using some heuristics. Then you need to define some input parameters at most.

Even defining some macros would help in writing this wrapper class.

Olaf Dietsche
  • 66,104
  • 6
  • 91
  • 177
  • Yup, I think this is probably the answer, I had a quick google to see if there was any specific tool for doing this and didn't see anything. Any suggestions would be welcome before I churn out something in Python. – dpwr Jan 27 '13 at 01:23
  • @dpwrussell Searching for `c++ class generator` doesn't yield something useful. I've done this myself in the past, but have always written from scratch. Depending on how complex your `QueryService` is, you might want to look into a real C++ parser http://llvm.org or http://www.antlr.org, they have a grammar for C++ available. – Olaf Dietsche Jan 27 '13 at 01:33
0

Briefly, If your aim is to encapsulate the functionality completely so that WeirdInt and QueryService are not exposed to the 'client' code such that you don't need to include any headers which declare them in the client code, then I doubt the approach you take will be able to benefit from any magic.

When I've done this before, my first step has been to use the pimpl idiom so that your header contains no implementation details as follows:

QueryServiceWrapper.h

class QueryServiceWrapperImpl;

class QueryServiceWrapper
{
public:
    QueryServiceWrapper();
    virtual ~QueryServiceWrapper();

    int getId();
private:
    QueryServiceWrapperImpl impl_;
};

and then in the definition, you can put the implementation details, safe in the knowledge that it will not leach out to any downstream code:

QueryServiceWrapper.cpp

struct QueryServiceWrapperImpl
{
public:
    QueryService svc_;
};

// ...

int QueryServiceWrapper::getValue()
{
    return impl_->svc_.getId().getValue();
}

Without knowing what different methods need to be employed to do the conversion, it's difficult add too much more here, but you could certainly use template functions to do conversion of the most popular types.

The downside here is that you'd have to implement everything yourself. This could be a double edged sword as it's then possible to implement only that functionality that you really need. There's generally no point in wrapping functionality that is never used.

I don't know of a 'silver bullet' that will implement the functions - or even empty wrappers on the functions. I've normally done this by a combination of shell scripts to either create the empty classes that I want or taking a copy of the header and using text manipulation using sed or Perl to change original types to the new types for the wrapper class.

It's tempting in these cases to use public inheritance to enable access to the base functions while allowing functions to be overridden. However, this is not applicable in your case as you want to change return types (not sufficient for an overload) and (presumably) you want to prevent exposure of the original Weird types.

The way forward here has to be to use aggregation although in such as case there is no way you can easily avoid re-implementing (some of) the interfaces unless you are prepared to automate the creation of the class (using code generation) to some extent.

Community
  • 1
  • 1
Component 10
  • 9,649
  • 4
  • 42
  • 58
-1

more complex approach is to introduce a required number of facade classes over original QueryService, each of which has a limited set of functions for one particular query or query-type. I don't know that your particular QueryService do, so here is an imaginary example:

  • suppose the original class have a lot of weired methods worked with strange types

    struct OriginQueryService
    {
        WeirdType1 query_for_smth(...);
        WeirdType1 smth_related(...);
    
        WeirdType2 another_query(...);
        void smth_related_to_another_query(...);
    
        // and so on (a lot of other function-members)
    };
    
  • then you may write some facade classes like this:

    struct QueryFacade
    {
        OriginQueryService& m_instance;
    
        QueryFacade(OriginQueryService* qs) : m_instance(*qs) {}
    
        // Wrap original query_for_smth(), possible w/ changed type of
        // parameters (if you'd like to convert 'em from C++ native types to
        // some WeirdTypeX)...
        DesiredType1 query_for_smth(...);
        // more wrappers related to this particular query/task
        DesiredType1 smth_related(...);
    };
    
    struct AnotherQueryFacade
    {
        OriginQueryService& m_instance;
    
        AnotherQueryFacade(OriginQueryService* qs) : m_instance(*qs) {}
    
        DesiredType2 another_query(...);
        void smth_related_to_another_query(...);
    };
    

    every method delegate call to m_instance and decorated w/ input/output types conversion in a way you want it. Types conversion can be implemented as @Jack describe in his post. Or you can provide a set of free functions in your namespace (like Desired fromWeird(const Weired&); and Weired toWeired(const Desired&);) which would be choosen by ADL, so if some new type arise, all that you have to do is to provide overloads for this 2 functions... such approach work quite well in boost::serialization.

    Also you may provide a generic (template) version for that functions, which would call getValue() for example, in case if lot of your Weired types has such member.

zaufi
  • 5,946
  • 21
  • 32