Replacing virtual With std::variant<> (In Real Life)

Classic OO Design

Source Hierarchy

#pragma once

#include <string>


class Source
{
public:
    virtual ~Source() = default;
    virtual const char* get() = 0;
};
#pragma once

#include "source.h"

class SourceCopyable : public Source
{
public:
    SourceCopyable(const std::string& resource);

    const std::string& resource() const { return _resource; }
    const char* get() override;

private:
    std::string _resource;
};
#pragma once

#include "source.h"


class SourceMovable : public Source
{
public:
    SourceMovable(const char* resource);
    SourceMovable(SourceMovable&&);
    SourceMovable& operator=(SourceMovable&&);
    ~SourceMovable();

    const char* resource() const { return _resource; }
    const char* get() override;

private:
    char* _resource;
};

Sink Hierarchy

#pragma once

#include <string>

class Sink
{
public:
    virtual ~Sink() = default;
    virtual void put(const std::string&) = 0;
};
#pragma once

#include "sink.h"


class SinkCopyable : public Sink
{
public:
    SinkCopyable(const std::string& resource);

    const std::string& resource() const { return _resource; }
    void put(const std::string& s) override;

private:
    std::string _resource;
};
#pragma once

#include "sink.h"


class SinkMovable : public Sink
{
public:
    SinkMovable(const char* resource);
    SinkMovable(SinkMovable&&);
    SinkMovable& operator=(SinkMovable&&);
    ~SinkMovable();

    const char* resource() const { return _resource; }
    void put(const std::string& s) override;

private:
    char* _resource;
};

Loop In The Middle

#pragma once

#include "source.h"
#include "sink.h"

class Loop
{
public:
    Loop(Source* source, Sink* sink)
    : _source(source), _sink(sink) {}

    void doit(unsigned ntimes)
    {
        while (ntimes--) {
            std::string s = _source->get();            // <-- dynamic dispatch
            _sink->put(s);                             // <-- dynamic dispatch
        }
    }

private:
    Source* _source;                                   // <-- interface
    Sink* _sink;                                       // <-- interface
};

Omitting virtual: Cram All Alternatives Into std::variant<>

Source Non-Hierarchy

#pragma once

#include <string>
#include <variant>


template <class... Sources>
class Source
{
public:
    Source() = default;

    template <class ConcreteSource>
    Source(ConcreteSource s)
    : _manyfold(s) {}

    std::string get()
    {
        return std::visit(Visitor(), _manyfold);
    }

private:
    struct Visitor
    {
        std::string operator()(auto source) { return source.get(); }
    };

private:
    std::variant<Sources...> _manyfold;
};
#pragma once

#include <string>


class SourceA
{
public:
    std::string get()
    {
        return "Source A";
    }
};
#pragma once

#include <string>


class SourceB
{
public:
    std::string get()
    {
        return "Source B";
    }
};

Sink Non-Hierarchy

#pragma once

#include <string>
#include <variant>


template <class... Sinks>
class Sink
{
public:
    Sink() = default;

    template <class ConcreteSink>
    Sink(ConcreteSink s)
    : _manyfold(s) {}

    void put(const std::string& str)
    {
        return std::visit(Visitor(str), _manyfold);
    }

private:
    struct Visitor
    {
        Visitor(const std::string& str) : str(str) {}
        void operator()(auto sink) { return sink.put(str); }
        const std::string& str;
    };

private:
    std::variant<Sinks...> _manyfold;
};
#pragma once

#include <string>
#include <iostream>


class Sink1
{
public:
    void put(const std::string& s)
    {
        std::cout << "Sink1: " << s << std::endl;
    }
};
#pragma once

#include <string>
#include <iostream>


class Sink2
{
public:
    void put(const std::string& s)
    {
        std::cout << "Sink2: " << s << std::endl;
    }
};

Loop In The Middle

#pragma once

#include "source.h"
#include "sink.h"


template <class Source, class Sink>
class Loop
{
public:
    Loop(Source& source, Sink& sink)
    : _source(source), _sink(sink) {}

    void doit(unsigned ntimes)
    {
        while (ntimes--) {
            std::string s = _source.get();             // <-- visiting std::variant
            _sink.put(s);                              // <-- visiting std::variant
        }
    }

private:
    Source& _source;                                   // <-- std::variant
    Sink& _sink;                                       // <-- std::variant
};