C++ Errors declaring Interface with return template

Viewed 73

I have a base interface, declaration like this - IBaseTest.h:

#pragma once
template <class T1>
class IBaseTest
{
public:

    virtual ~IBaseTest();
    virtual T1 DoSomething() = 0;
};

And two children who overrides DoSomething() CBaseTest1 claass in - BaseTest1.h:

#pragma once
#include "IBaseTest.h"
class CBaseTest1: public IBaseTest<int>
{
public:
    virtual int DoSomething();
};

BaseTest1.cpp:

#include "BaseTest1.h"

int CBaseTest1::DoSomething()
{
    return -1;
}

And CBaseTest2 in - BaseTest2.h

#pragma once
#include "IBaseTest.h"
class CBaseTest2: public IBaseTest<long long>
{
public:
    virtual long long DoSomething();
};

BaseTest2.cpp:

#include "BaseTest2.h"

long long CBaseTest2::DoSomething()
{
    return -2;
}

So CBaseTest1::DoSomething() overrides return type to int, and CBaseTest2::DoSomething() to long long. Now, i want to use a pointer to the base interface, to work with those classes, and there i have the problem:

#include "IBaseTest.h"
#include "BaseTest1.h"
#include "BaseTest2.h"


int _tmain(int argc, _TCHAR* argv[])
{
    IBaseTest<T1> * pBase = NULL;

    pBase = new CBaseTest1();

    cout << pBase->DoSomething() << endl;

    pBase = new CBaseTest2();

    cout << pBase->DoSomething() << endl;

    getchar();

    return 0;
}

The problem is i cannot declare IBaseTest<T1> * pBase = NULL; T1 is undefined. If declare the template before _tmain like this:

template <class T1>

int _tmain(int argc, _TCHAR* argv[])
{ 
  ...
}

I get: error C2988: unrecognizable template declaration/definition

So what do i put here instead of T1?

IBaseTest<??> * pBase = NULL;

2 Answers

The problem is that T1 parameter needs to be known when you instantiate an object of the template class IBaseTest. Technically, IBaseTest<int> and IBaseTest<long long> are two different types without a common base and C++ does not allow you to declare a variable IBaseTest<T1> pBase = NULL; where T1 is determined at runtime. What you are trying to achieve is something that would be possible in a dynamically typed language, but not in C++ because it is statically typed.

However, if you know the expected return type of DoSomething whenever you call that method, you can sort of make your example to work. First, you need to introduce a common base class that is not a template:

#include <typeinfo>
#include <typeindex>
#include <assert.h>

class IDynamicBase {
public:
  virtual std::type_index type() const = 0;

  virtual void doSomethingVoid(void* output) = 0;

  template <typename T>
  T doSomething() {
    assert(type() == typeid(T));
    T result;
    doSomethingVoid(&result);
    return result;
  }

  virtual ~IDynamicBase() {}
};

Note that it has a template method called doSomething that takes a type parameter for the return value. This is the method that we will call later.

Now, modify your previous IBaseTest to extend IDynamicBase:

template <class T1>
class IBaseTest : public IDynamicBase
{
public:
  std::type_index type() const {return typeid(T1);}

  void doSomethingVoid(void* output) {
    *(reinterpret_cast<T1*>(output)) = DoSomething();
  }

  virtual T1 DoSomething() = 0;
  virtual ~IBaseTest() {}
};

You don't need to change CBaseTest1 or CBaseTest2.

Finally, you can now write the code in your main function like this:

  IDynamicBase* pBase = nullptr;

  pBase = new CBaseTest1();

  std::cout << pBase->doSomething<int>() << std::endl;

  pBase = new CBaseTest2();

  std::cout << pBase->doSomething<long long>() << std::endl;

Note that instead of calling pBase->DoSomething(), we now call pBase->doSomething<T>() where T is a type that must be known statically where we call the method and we provide that type at the call site, e.g. pBase->doSomething<int>().

The language does not allows to do directly what you are trying to do. At that point, you should ask yourself if that is the right solution for the problem.

The first approach that might work well assuming that you don't have too much different operations to do for each type would be to simply do the action in the function itself instead of returning type that are not related through inheritance.

class IBaseTest
{
public:
    virtual void OutputTo(std::ostream &os) = 0;
};

class CBaseTest1
{
public:
    virtual void OutputTo(std::ostream &os) override;
private:
    int DoSomething();
};

void CBaseTest1OutputTo(std::ostream &os)
{
    os << DoSomething() << std::endl;
}

If you have only a few types but a lot of operation, you might use the visitor pattern instead.

If you mainly have operation that depends on type, you could use:

class IVisitor
{
public:
    virtual void Visit(int value) = 0;
    virtual void Visit(long value) = 0;
};

Otherwise, use that which is more general

class IVisitor
{
public:
    virtual void Visit (CBaseTest1 &test1) = 0;
    virtual void Visit (CBaseTest2 &test2) = 0;    
};

Then in your classes add an apply function

class IBaseTest
{
public:
    virtual void Apply(IVisitor &visitor) = 0;
};

In each derived class, you implement the Apply function:

void CBaseTest1 : public IBaseTest
{
    virtual void Apply(IVisitor &visitor) override 
    {
        visitor.Visit(this->DoSomething()); // If you use first IVisitor definition
        visitor.Visit(*this);               // If you use second definition
};

And for creation purpose, you could have a factory that return the appropriate class from a type tag if you need to create those class from say a file…

One example assuming you want a new object each time:

 enum class TypeTag { Integer = 1, LongInteger = 2 };
std::unique_ptr<IBaseTest> MakeObjectForTypeTag(TypeTag typeTag)
{
    switch (typeTag)
    {
        case TypeTag::Integer : return new CBaseTest1();
        case TypeTag::LongInteger : return new CBaseTest2();
    }
}

So the only time you would do a switch statement is when you are creating an object… You could also use a map or even an array for that...

The right approach depends on your actual problem.

  • How many CBaseClass* do you have?
  • Do you expect to add other classes? Often?
  • How many operations similar to DoSomething() do you have?
  • How many actions that works on the result of DoSomething do you have?
  • Do you expect to add other actions? Often?

By responding to those questions, it will be much easier to take the right decision. If the action are stables (and you only have a few one), then specific virtual functions like OutputToabove is more appropriate. But if you have dozen of operation but don't expect much changes to ITestBase class hierarchy, then visitor solution is more appropriate.

And the reason why a given solution is more appropriate in a given context is mainly the maintenance effort when adding classes or actions in the future. You typically want that the most frequent change (adding a class or an action) require les changes everywhere in the code.

Related