Search code examples
c++referencevariant

Storing an variant of references for a view type in C++


I have an environment where I have no C++17 (C++14 ATM) features nor boost.

Currently I have a class responsible for sending messages between services in our domain, this class uses multiple types of addressing (both types are non trivial) and one of them can be converted to other (lets say A can be converted to B). Class that is responsible for sending messages, contains of multiple (templated, because messages have no base class) methods, duplicated for both of the addressing types using function overload.

class Sender{
public:
// old API
template<typename TReq>
send(const A&target, TReq req){send(target, make_request{req});}

// old API
template<typename TReq>
send(const B&target, TReq req){sendB(target, make_request{req});}

protected:
// done for mocking/testing purpose only
virtual send(const A&target, MessageProxy message);
virtual sendB(const A&target, MessageProxy message);
};

Addressing A and B is done interchangeable by the users (they don't care if this is A or B type of address) In order to play nice with google mock I need to provide different names for overloaded methods (I could do this in mock, but decided to call them differently here, there is no difference)

The problem that I want to solve is simple. Provide a single virtual send method, that will take an "union" of references to address type A or B (think of it as variant<A&, B&>) that can be used the same way as templated versions of those methods, like.

virtual send(const TargetProxy &target, MessageProxy message);

that can be used

EXPECT_CALL(mock, send(Eq(A{}), Message{}))...
EXPECT_CALL(mock, send(Eq(B{}), OtherMessage{}))...

The type that carry A& or B& (TargetProxy) should hold an const Reference to A OR to B and should be used only to pass A or B from templated send to virtual send, but I failed to figure out a simple solution for this


Solution

  • There are lots of boilerplate missing, but PoC might be sth like this:

    #include <utility>
    #include <cassert>
    #include <iostream>
    
    struct A {};
    struct B {};
    
    struct EitherRef : private std::pair<A*, B*>
    {
        EitherRef(A& a)
        : std::pair<A*, B*>(std::addressof(a), nullptr){};
        EitherRef(B& b)
        : std::pair<A*, B*>(nullptr, std::addressof(b)){};
    
        bool hasA() const {return this->first!=nullptr;}
        bool hasB() const {return this->second;}
        operator A&() { assert (this->first);return *this->first; }
        operator B&() { assert (this->second);return *this->second; }
    
    };
    
    template <typename Func>
    void applyVisitor(EitherRef e, Func f)
    {
        if (e.hasA()) {
            f(static_cast<A&>(e));
        } else if (e.hasB()) {
            f(static_cast<B&>(e));
        }
    }
    
    void f(const EitherRef& r)
    {
        struct {
            void operator()(A&) {
                std::cout <<"A\n";
            }
            void operator()(B&) {
                std::cout <<"B\n";
            }
        } v;
        applyVisitor(r, v);
    }
    
    int main(int, char*[])
    {
        A a1;
        B b1;
        EitherRef ra{a1};
        EitherRef rb{b1};
        f(ra);
        f(rb);
    
        return 0;
    }