'Optimization-friendly Polymorphic NVI adapter class for Runtime Polymorphism (customizable ownership)

I have a polymorphic NVI class to provide an adapter for Runtime-Polymorphism.
Type-erasure is used for loose coupling.

The non-owning implementation can be optimized out in a compile-time context:

// usage just for reference

printer referenced{"Referenced"};
const auto &polymorphicPrinter = polymorphic(referenced);
polymorphicPrinter.print(8.15);

// how to optimize this?
const auto &owningPolymorhic = polymorphic(std::make_unique<printer>("Owned");

However, I am not sure what is a simple or elegant way to allow passing the ownership and still have optimization like in the above case when it is possible.

Here is my naive implementation. I also added std::bad_function_call for invalid state.


// api_header.h
#pragma once

#include <memory>
#include <string_view>
#include <functional>

class polymorphic final
{
public:

    template <typename T>
    polymorphic(const T& t)
        : pVTable_(getVTable(t)),
        pObj_(std::addressof(t), [](const void*){})
    {}

    template <typename T>
    polymorphic(std::unique_ptr<const T> t)
        : pVTable_(getVTable(*t)),
        pObj_(t.release(), [](const void* pObj){
            delete static_cast<const T*>(pObj);
        })
    {}

    template <typename T>
    polymorphic(std::unique_ptr<T> t)
        : polymorphic(std::unique_ptr<const T>(t.release()))
    {}

    polymorphic(const polymorphic&) = delete;
    polymorphic& operator=(const polymorphic&) = delete;

    polymorphic(polymorphic&& other) noexcept
        : pVTable_(other.pVTable_),
        pObj_(std::move(other.pObj_))
    {
        other.pVTable_ = polymorphic::getInvalidVTable();
    }

    polymorphic& operator=(polymorphic &&other) noexcept
    {
        pVTable_ = other.pVTable_;
        pObj_ = std::move(other.pObj_);

        other.pVTable_ = polymorphic::getInvalidVTable();

        return *this;
    }
    
    template <typename T>
    void print(T v) const
    {
        pVTable_->print(pObj_.get(), v);
    }

private:

    polymorphic() = default;

    struct v_table {
        virtual void print(const void *, double) const
        {
            throw std::bad_function_call();
        }
        virtual void print(const void *, std::string_view) const
        {
            throw std::bad_function_call();
        }

    protected:
        ~v_table() = default;
    };

    static const v_table *getInvalidVTable()
    {
        struct : v_table {} constexpr static invalidVTable{};
        return &invalidVTable;
    }

    template <typename T>
    static const v_table *getVTable(const T&)
    {
        struct : v_table {
            void print(const void *pObj, double v) const override 
            {
                static_cast<const T*>(pObj)->print(v);
            }

            void print(const void *pObj, std::string_view v) const override 
            {
                static_cast<const T*>(pObj)->print(v);
            }

        } constexpr static vTable{}; 

        return &vTable;
    }

private:
    const v_table *pVTable_ = getInvalidVTable();

    // TODO: optimisation-friendly?
    std::unique_ptr<const void, std::function<void(const void*)>> pObj_;
};

// main.cpp
#include "api_header.h"

#include <cstddef>
#include <string>
#include <iostream>

class printer
{
public:

    template <size_t L>
    explicit printer(const char (&name)[L])
        : name_(name, L)
    {}

    void print(int v) const
    {
        std::cout << name_ << ": " << v << std::endl;
    }

    void print(double v) const
    {
        std::cout << name_ << ": " << v << std::endl;
    }

    void print(std::string_view v) const
    {
        std::cout << name_ << ": " << v << std::endl;
    }

    ~printer()
    {
        std::cout << name_ << " destroyed\n";
    }

private:
    std::string name_;
};

int main()
{
    printer pReferenced{"referenced"};

    {
        const auto &polyReferenced = polymorphic(pReferenced);
        polyReferenced.print(4);
        polyReferenced.print(8.15);
        polyReferenced.print("oceanic");
        std::cout << "non-owning polymorphic destroyed\n\n";
    }

    {
        const auto &polyOwned = polymorphic(std::make_unique<printer>("owned"));
        polyOwned.print(4);
        polyOwned.print(8.15);
        polyOwned.print("oceanic");
    }
    std::cout << "owning polymorphic destroyed\n\n";

    std::cout << "Invalidating polymorphic reference\n";
    try {
        auto invalidReferenced = polymorphic(pReferenced);
        auto polyMoved = std::move(invalidReferenced);

        polyMoved.print("I have been moved");
        invalidReferenced.print("This will throw");
    } 
    catch (const std::bad_function_call &e)
    {
        std::cout << "error: " << e.what() << std::endl;
    }
}

This code: https://godbolt.org/z/Pc8981ns8

Is it possible to make this code optimization-friendly?



Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source