BobAlmond
BobAlmond

Reputation: 459

Deduce return type of a function on derived class automatically on base class

I would like to achieve something like following on c++14, basically derived class can have different type of return type (e.g int, double, string, etc)

class Base {
public:
   virtual auto value() = 0; // I know this won't compile
};

class Derived1 : public Base {
public:
   double value() override { return 1.0; };
};                                                                                                                      
class Derived2 : public Base {
public:
   int value() override { return 1; };
};

I know above code won't compile but I'm trying to achieve something like that using any possible way or pattern (I've tried template, CRTP, visitor but nothing can satisfy my following code)

Derived1 d1;
Derived2 d2;
std::vector<Base*> base = { &d1, &d2 };

for (const auto* b : base)
    std::cout << b->value();

The best I can get with template is something like

Derived1 d1;
Derived2 d2;
std::vector<Base*> base = {&d1, &d2);

for (const auto* b : base) 
    if (dynamic_cast<Derived1>(b))
        std::cout << b->value<double>();
    else if (dynamic_cast<Derived2>(b))
        std::cout << b->value<int>();

but if I have 100 types of Derived class, it won't look that pretty :D

Upvotes: 2

Views: 1705

Answers (2)

aayushp
aayushp

Reputation: 1

An abstract base class is generally intended as a public interface to the implementation class. In this case your interface is changing with every child: when the return type changes, the function signature changes and that is why override will result in compilation error as you might have already realized.

Visitor is useful provided your class system is stable as described here:

  1. Confirm that the current hierarchy (known as the Element hierarchy) will be fairly stable and that the public interface of these classes is sufficient for the access the Visitor classes will require. If these conditions are not met, then the Visitor pattern is not a good match.

To implement Visitor, you would generally define multiple functions with different input parameter types instead of using a dynamic cast (as described on the link above).

You can also do away with the class inheritance altogether. Check out Sean Parent's talk. He describes a similar use case and uses template to do what you might be trying to do. The trick is to define one class with a templated constructor and a on object type to use with the constructor.

Upvotes: 0

Sam Varshavchik
Sam Varshavchik

Reputation: 118300

This is not possible, fundamentally, in C++. C++ does not work this way, for the following simple reason. Let's just pretend that this works somehow. Consider the following simple function:

void my_function(Base *p)
{
    auto value=p->value();
}

Now, ask yourself: what is value's type, here? You may not be aware of this, but there is no such actual type called auto in C++. auto is a placeholder for the C++ compiler to deduce, or determine the actual type at compile time. auto basically says: whatever the expression's type evaluates to be, that's the type of this object. If your C++ compiler determined that p->value() returns an int, then value is an int, and the above is 100% equivalent to declaring int value=p->value();.

Here, it is impossible to determine values actual type. Is it an int? Is it a double? Or something else?

Unfortunately, it's a mystery that will remain unsolved forever. The actual value of type depends on the derived object that the pointer to the Base actually points to, which is unknown at compile-time, and can only be determine at run time.

It is a fundamental property of C++ that the types of all objects must be deduced at compile time. This is baked-into C++. There are no workarounds. There are no alternatives. What you are trying to do cannot be done in C++.

However, there's a little bit of good news: if the number of possible return types is limited, just declare an ordinary virtual method that returns a std::variant, and each derived class can then return an appropriate value. It will be up to the caller to make use of it.

In the case above, this would be:

class Base {
 public:
   virtual std::variant<int, double> value() = 0;
};

If the type of the actual value being returned is completely unknown, then I suppose you can use std::any. In either case, as you attempt to implement either approach you will discover that C++ will force you to figure out and check for each possible type (in ways that depend on whether you use std::variant or std::any), each time you attempt to use the value returned from this method.

Upvotes: 3

Related Questions