1

I would like to have a class template with basic implementations of my methods, and a set of subclasses that use the template class with particular types (double, int, char*) and override some subset of those base implementations, as needed. However, my overridden methods don't seem to get called unless I declare the object as an instance of the subclass. In exploring this problem I came up with the following code:

#include <iostream>

template <typename T>
class BaseClass {
public:
    virtual void print1 (T thing) {
        std::cout << "Base print1: " << thing << std::endl;
    }

    virtual void print2 (T thing) {
        std::cout << "Base print2: " << thing << std::endl;
    }
};

class IntClass : public BaseClass<int> {
public:
    void print2 (int thing) {
        std::cout << "Int print2: " << thing << std::endl;
    }
};

int main()
{
    BaseClass<int> thing = IntClass();
    thing.print1(1);
    thing.print2(1);

    IntClass thing2 = IntClass();
    thing2.print1(2);
    thing2.print2(2);

    return 0;
}

My expected output would be:

Base print1: 1
Int print2: 1
Base print1: 2
Int print2: 2

But instead, I get:

Base print1: 1
Base print2: 1
Base print1: 2
Int print2: 2

Is it possible to achieve my goal here, or am I better off creating separate base classes for each type in this case? Apologies if my terminology is a bit off - I am relatively new to C++.

3 Answers3

7

The type of thing is BaseClass<int>, not IntClass. The temporary IntClass you used to initialise it has been destroyed, after copying the BaseClass sub-object. Copying part of an object like this is sometimes known as slicing.

Polymorphism works when you use references or pointers to a base class, which may refer to an object of a derived class. So you'd get the expected result with a reference to an IntClass object:

// In C++11 or later, you can bind an rvalue reference to a temporary, extending its lifetime
BaseClass<int> && thing = IntClass();

// If you're stuck in the past, you can bind an lvalue reference to a variable
IntClass ic;
BaseClass<int> & thing = ic;
Mike Seymour
  • 249,747
  • 28
  • 448
  • 644
  • Ok, that makes sense. But I am having trouble getting your suggested line of code to work. Am I missing something? `error: expected unqualified-id before && token` – diagonalfish Mar 06 '15 at 02:14
  • 1
    @diagonalfish: If you've got an ancient compiler, or haven't specified a C++11 or C++14 dialect, then it might not know what an *rvalue* reference is. In that case, you could try `IntClass ic; BaseClass & thing = ic;` or faff around with dynamic allocation as another answer suggests. – Mike Seymour Mar 06 '15 at 02:16
  • Aha, yep, -std=c++11 did it. Awesome. Thanks. – diagonalfish Mar 06 '15 at 02:19
7

BaseClass<int> thing = IntClass();

This doesn't do what you think it does. It creates a temporary instance of IntClass and then copies it into thing which is of type BaseClass<int>. The result is called object-slicing (you can search for that).

In this case, as the derived type is known at compile-time, there's no reason not to use IntClass as the type of thing. But for general usage, you need to use pointers. Here is an example:

std::unique_ptr<BaseClass<int>> thing(new IntClass);
thing->print1(1);
Neil Kirk
  • 21,327
  • 9
  • 53
  • 91
2

If you were to use a pointer, you would achieve your expected results. But, as short research for this answer has learned me, what has happened here is that 'thing' -- the object called thing; not the parameter, takes the part of IntClass that is of BaseClass and simply discards the rest. This is called object slicing.

user4578093
  • 231
  • 1
  • 3
  • 10