Adapting strongly typed enum to ints without abstraction overhead

There is a code like below: #include #include #include #include template struct IPushChannel { virtual void push(T) = 0; }; template struct PushChannel : IPushChannel { void push(T) override { std::cout

Mar 19, 2025 - 18:11
 0
Adapting strongly typed enum to ints without abstraction overhead

There is a code like below:

#include 
#include  
#include 
#include 

template 
struct IPushChannel {
    virtual void push(T) = 0;
};

template 
struct PushChannel : IPushChannel {
    void push(T) override 
    {
        std::cout << "push\n";
        // do sth
    }
};

template 
std::shared_ptr> getChannel(std::string channel_id)
{
   // For the purpose of question let's allocate a channel 
   // normally it performs some lookup based on id

   static auto channel = std::make_shared>();

   return channel;
}

enum class SomeEnum { E1, E2, E3 };

Below is V1 code

namespace v1 {    
    template 
    struct PushProxy {
        PushProxy(std::shared_ptr> t) : ptr_{t} {}
    
        void push(T val) 
        {
            if (auto ptr = ptr_.lock())
            {
                ptr->push(val);
            }
            else {
                std::cout << "Channel died\n";
            }
        }
    
        std::weak_ptr> ptr_;
    };
    

    template 
    struct EnumAdapter : IPushChannel {
        EnumAdapter(std::shared_ptr> ptr) : ptr_{ptr} {}
    
        void push(T) 
        {
            ptr_.lock()->push(static_cast(123));
        }
    
        std::weak_ptr> ptr_;
    };
    

    template 
    PushProxy getProxy(std::string channel_id) {
        if constexpr (std::is_enum_v) {
            auto channel = getChannel(channel_id);
            auto adapter = std::make_shared>(channel);
            return PushProxy{adapter};       
        }     
        else {
            return PushProxy{getChannel(channel_id)};
        }
    }
}

Below is V2 code

namespace v2 {    
    template 
    struct PushProxy {
        template 
        PushProxy(Callable func) : ptr_{func} {}
    
        void push(T val) 
        {
            if (auto ptr = ptr_())
            {
                ptr->push(val);
            }
            else {
                std::cout << "Channel died\n";
            }
        }
    
        std::function>()> ptr_;
    };
    
    template 
    struct WeakPtrAdapter
    {
        std::shared_ptr operator()()
        {
            return ptr_.lock();
        }

        std::weak_ptr> ptr_;
    };

    template 
    struct EnumAdapter {
        struct Impl : public IPushChannel {
            void useChannel(std::shared_ptr> channel)
            {
                // Keep the channel alive for the upcoming operation.
                channel_ = channel;
            }

            void push(T value)
            {
                channel_->push(static_cast(value));

                // No longer needed, reset.
                channel_.reset();
            }

            std::shared_ptr> channel_;
        };

        std::shared_ptr> operator()()
        {
            if (auto ptr = ptr_.lock())
            {
                if (!impl_) {
                    impl_ = std::make_shared();                        
                }
                // Save ptr so that it will be available during the opration
                impl_->useChannel(ptr);

                return impl_;
            }
            impl_.reset();
            return nullptr;
        }

        std::weak_ptr> ptr_;
        std::shared_ptr impl_;
    };
    

    template 
    PushProxy getProxy(std::string channel_id) {
        if constexpr (std::is_enum_v) {
            return PushProxy{EnumAdapter{getChannel(channel_id)}};       
        }     
        else {
            return PushProxy{WeakPtrAdapter{getChannel(channel_id)}};
        }
    }
}

Main

void foo_v1()
{
  auto proxy = v1::getProxy("channel-id");
  proxy.push(SomeEnum::E1);
}

void foo_v2()
{
  auto proxy = v2::getProxy("channel-id");
  proxy.push(SomeEnum::E1);
}

int main()
{
    foo_v1();
    foo_v2();
}

Let's say that channels are entities that allow to push some data through it. Supported types are e.g. int, std::string but also enums. Let's assume that channels are fetched from some external service and enums are abstracted as ints.

In my library I want to give the user to opportunity to operate on strongly typed enums not ints, the problem is that in the system these channels have been registered as ints.

When the user calls for channel's proxy, it provides the Proxy's type, so for e.g. int it will get PushProxy, for MyEnum, it will get PushProxy

However, as you can see when the user wants to get enum proxy, the library looks for int channel, thus I cannot construct PushProxy with IPushChannel because the type does not match.

So I though that maybe I could introduce some adapter that will covert MyEnum to int, so that user will use strongly types enum PushProxy where the value will be converted under the hood.

The channels in the system can come and go so that's why in both cases I use weak_ptr.

V1 In V1 the problem is that I cannot simply allocated EnumAdapter and pass it to PushProxy because it gets weak_ptr, which means that the EnumAdapter will immediately get destroyed. So this solution does not work at all.

V2 In V2 the solution seems to be working fine, however the problem is that there can be hundreds of Proxies to the same channel in the system, and each time the Proxy gets constructed and used, there is a heap allocation for EnumAdapter::Impl. I'm not a fan of premature optimization but simply it does not look well.

What other solution would you suggest? I would like to avoid storing adapter somewhere externally. Also it would only solve the problem partially as the Adapter would be allocated on the heap each time one gets an access to proxy.