c++c++17

Type erasure on a pointer-to-member for a data type


Assuming a struct containing a number of variables:

struct A {
  int a;
  bool b;
  string c;
};

Supposedly, I need to map them out to store in a single collection

auto ptr_= reinterpret_cast<UniversalType>(&A::a);

What would be the universal type to cast to and from a pointer to any of the members in order to emulate type erasure?

Can uintptr_t be used for that?

PS. It's emebdded project with no RTTI support. So assumptions:

  1. It's an XY-problem
  2. You can use big 3-rd party library
  3. You can use wrappers with dynamic_cast

are wrong. It's the original problem existing with portability of pre-existing codebase. Dynamic dispatch is used in some cases and is used currently but it's manual (using function pointers) and ineffective. Compile-time dispatch is possible.


Solution

  • You may simply invent your own type erasure wrapper, like this:

    class MemberPtr {
    private:
        struct AbstractMemberPtr {
            virtual ~AbstractMemberPtr() = default;
        };
    
        template<typename R, typename T>
        struct MemberPtrWrapper: AbstractMemberPtr {
            R T::* ptr;
            explicit MemberPtrWrapper(R T::* ptr) noexcept : ptr{ ptr } {}
            R T::* operator*() const noexcept { return ptr; }
        };
    
        std::unique_ptr<AbstractMemberPtr> m_ptr;
    
    public:
        template<typename R, typename T>
        MemberPtr(R T::* memberPtr): m_ptr{ std::make_unique<MemberPtrWrapper<R,T>>(memberPtr) }
        {
        }
    
        template<typename R, typename T>
        R T::* RawPtr() const {
            if (const auto* const ptr = dynamic_cast<MemberPtrWrapper<R, T>*>(m_ptr.get())) {
                return *(*ptr);
            } else {
                return nullptr;
            }
        }
    };
    
    

    In a collection it can be used like this:

    std::vector<MemberPtr> ptrs;
    ptrs.push_back(MemberPtr{&A::a});
    ptrs.push_back(MemberPtr{&A::c});
    

    And then extracted like this:

    if (const auto strPtr = ptrs.back().RawPtr<std::string, A>()) {
        A a;
        a.*(strPtr) = "some string";
    }
    

    Alternatively (if you don't need to deduce all members automagically), you can simply use std::variant:

    using MembersVariant = std::variant<decltype(&A::a), decltype(&A::b), decltype(&A::c)>;
    std::vector<MembersVariant> ptrs;
    ptrs.emplace_back(&A::a);
    ptrs.emplace_back(&A::b);
    
    A a;
    // Visitor is a helper utility class, which looks as follows:
    // template<typename... T> struct Visitor : T... { using T::operator()...; };
    std::visit(Visitor{
        [&a](std::string A::* memberPtr) {
            a.*memberPtr = "some string";
        },
        [&a](bool A::* memberPtr) {
            a.*memberPtr = true;
        },
        [&a](int A::* memberPtr) {
            a.*memberPtr = 2;
        }
    }, ptrs.back());