views:

91

answers:

4

Given the following template:

template <typename T>
class wrapper : public T {};

What visible differences in interface or behaviour are there between an object of type Foo and an object of type wrapper<Foo>?

I'm already aware of one:

  • wrapper<Foo> only has a nullary constructor, copy constructor and assignment operator (and it only has those if those operations are valid on Foo). This difference may be mitigated by having a set of templated constructors in wrapper<T> that pass values through to the T constructor.

But I'm not sure what other detectable differences there might be, or if there are ways of hiding them.


(Edit) Concrete Example

Some people seem to be asking for some context for this question, so here's a (somewhat simplified) explanation of my situation.

I frequently write code which has values which can be tuned to adjust the precise performance and operation of the system. I would like to have an easy (low code overhead) way of exposing such values through a config file or the user interface. I am currently writing a library to allow me to do this. The intended design allows usage something like this:

class ComplexDataProcessor {
    hotvar<int> epochs;
    hotvar<double> learning_rate;
public:
    ComplexDataProcessor():
        epochs("Epochs", 50),
        learning_rate("LearningRate", 0.01)
        {}

    void process_some_data(const Data& data) {
        int n = *epochs;
        double alpha = *learning_rate;
        for (int i = 0; i < n; ++i) {
            // learn some things from the data, with learning rate alpha
        }
    }
};

void two_learners(const DataSource& source) {
    hotobject<ComplexDataProcessor> a("FastLearner");
    hotobject<ComplexDataProcessor> b("SlowLearner");
    while (source.has_data()) {
        a.process_some_data(source.row());
        b.process_some_data(source.row());
        source.next_row();
    }
}

When run, this would set up or read the following configuration values:

FastLearner.Epochs
FastLearner.LearningRate
SlowLearner.Epochs
SlowLearner.LearningRate

This is made up code (as it happens my use case isn't even machine learning), but it shows a couple of important aspects of the design. Tweakable values are all named, and may be organised into a hierarchy. Values may be grouped by a couple of methods, but in the above example I just show one method: Wrapping an object in a hotobject<T> class. In practice, the hotobject<T> wrapper has a fairly simple job -- it has to push the object/group name onto a thread-local context stack, then allow the T object to be constructed (at which point the hotvar<T> values are constructed and check the context stack to see what group they should be in), then pop the context stack.

This is done as follows:

struct hotobject_stack_helper {
    hotobject_stack_helper(const char* name) {
        // push onto the thread-local context stack
    }
};

template <typename T>
struct hotobject : private hotobject_stack_helper, public T {
    hotobject(const char* name):
        hotobject_stack_helper(name) {
        // pop from the context stack
    }
};

As far as I can tell, construction order in this scenario is quite well-defined:

  1. hotobject_stack_helper is constructed (pushing the name onto the context stack)
  2. T is constructed -- including constructing each of T's members (the hotvars)
  3. The body of the hotobject<T> constructor is run, which pops the context stack.

So, I have working code to do this. There is however a question remaining, which is: What problems might I cause for myself further down the line by using this structure. That question largely reduces to the question that I'm actually asking: How will hotobject behave differently from T itself?

+3  A: 

Strange question, since you should be asking questions about your specific usage ("what do I want to do, and how does this help me or hurt me"), but I guess in general:

wrapper<T> is not a T, so:

  • It can't be constructed like a T. (As you note.)
  • It can't be converted like a T.
  • It loses access to privates T has access to.

And I'm sure there are more, but the first two cover quite a bit.

GMan
+1 for the third bullet
Chubsdad
A: 

A reference to an object is convertible (given access) to a reference to a base class subobject. There is syntactic sugar to invoke implicit conversions allowing you to treat the object as an instance of the base, but that's really what's going on. No more, no less.

So, the difference is not hard to detect at all. They are (almost) completely different things. The difference between an "is-a" relationship and a "has-a" relationship is specifying a member name.

As for hiding the base class, I think you inadvertently answered your own question. Use private inheritance by specifying private (or omitting public for a class), and those conversions won't happen outside the class itself, and no other class will be able to tell that a base even exists.

Potatoswatter
I'm not trying to hide the base class, I'm trying to make wrapper<T> look as much like T as possible (I've now added a long explanation of my actual usage in the question)
John Bartholomew
I see. Well, it will look like the base as far as references and pointers being implicitly convertible, and access to the base's members. Which you know already. But aside from that, they will produce different template specializations, etc. It's still not clear what kind of differences you're trying to eliminate. The simple answer I'm giving is that they are entirely different. What you seem to be concerned with is *compatibility*, and then we need to know what sorts of interfaces they work with.
Potatoswatter
Yes, that's fair -- I guess I may simply have to suck it and see.
John Bartholomew
A: 

If your inherited class has its own member variables (or at least one), then

sizeof(InheritedClass) > sizeof(BaseClass)
Benoît
`struct BaseClass {}; struct InheritedClass : BaseClass {}; static_assert(sizeof(BaseClass) == sizeof(InheritedClass), ":P");`
GMan
@Benoit: technically, non-static member variables. Anyway, `sizeof` doesn't provide a whole lot of information on the class hierarchy.
Potatoswatter
@Potatoswatter : true@GMan : i clearly stated the hypothesis that the inherited class should have member variables (static ;-) ) for my assertion to be true
Benoît
+2  A: 

Suppose you have:

class Base {};
class Derived : Base {};

Now you can say:

Base *basePtr = new Derived;

However, you cannot say:

wrapper<Base> *basePtr = new wrapper<Derived>();

That is, even though their type parameters may have an inheritance relationship, two types produced by specialising a template do not have any inheritance relationship.

Daniel Earwicker