Runtime polymorphism without dynamic memory allocation (part 2)

Just a follow-up on Runtime polymorphism without dynamic memory allocation for the reason it’s C++ and you can do all kinds of weird things. This is a fun article. I didn’t think it through.

Last time, I wanted a clear API for the caller and I created an abstraction above std::variant. I did it by returning a lambda that the caller can simply call with the needed arguments.

Then I thought “What if I can do it even more simple?”. I’d like to have the same API as the implementation with the pointer.

object->function(argument);

But without heap allocations.

The storage remains a variant. And I return a pointer to the currently selected type in the variant.

#include <cassert>
#include <variant>

struct P {
    virtual int f(int) const = 0;
    virtual ~P() = default;
};
 
struct A : P {
    int f(int in) const override {return in + 1;}
};
 
struct B : P {
    int f(int in) const override {return in + 2;};
};

struct factory {
    std::variant<A, B> object;

    P* create(char o) {  
        switch(o) {
            case 'a': object = A{}; break;
            default: object = B{}; break;
        }

        return std::visit([](auto& obj) -> P* { return &obj; }, object);
    }
};

int main() {
    factory f{};
    assert(f.create('a')->f(1) == 2);
    assert(f.create('b')->f(1) == 3);
}

 

A particular thing is that I have to use trailing return type for the lambda visitor. This is because “std::visit requires the visitor to have the same return type for all alternatives of a variant” (Clang). So I return all objects through the base class. I’m back to virtual inheritance functions.

Besides the raw pointer, I’m wondering what could go wrong with this approach.