Skip to main content
deleted 2 characters in body
Source Link

The design assumptions I've made are threetwo-fold:

The design assumptions I've made are three-fold:

The design assumptions I've made are two-fold:

added 276 characters in body
Source Link
struct iFace
{
    virtual int meth() = 0;
};

struct Impl1 : public iFace
{
    int m_a;
    inline int meth() override { return m_a; }
};

struct Impl2 : public iFace
{
    int m_a, m_b;
    inline int meth() override { return m_a + m_b; }
};

struct PolyOp
{
    //this is only required for inferring the return-type expected by the interface
    int operator()(iFace&);
    inline int operator()(Impl1& impl1)
    {
        return impl1.meth();
    }

    //template impl because default, specific instances become overrides
    template<typename Impl>
    inline int operator()(Impl& impl2)
    {
        return impl2.meth() - impl2.m_b;
    }
};
TEST(Basic, HybridPolyContainer)
{
    Impl2 impl;
    std::tie(impl.m_a, impl.m_b) = std::pair{3, 2};
    iFace* ref = &impl;
    ImplRef< iFace, Impl1, 0 > ir1(ref);
    ImplRef< iFace, Impl2, 0 > ir2(ref);
    assert(ir1.instance() == -1);
    assert(ir2.instance() == 0);
    OpaqueImplCollector< iFace, Impl2, Impl1 > implContainer(ref);
    assert(implContainer.m_idx == 1);
    PolyOp polyop;
    assert(implContainer.apply(polyop) == 3); // impl.m_a);
    
    Impl1 implOne;
    implOne.m_a = 7;
    ref = &implOne;
    OpaqueImplCollector< iFace, Impl2, Impl1 > implContainerOne(ref);
    assert(implContainerOne.m_idx == 0);
    // does not access template operator, access specific overload
    assert(implContainerOne.apply(polyop) == 7);

};
struct iFace
{
    virtual int meth() = 0;
};

struct Impl1 : public iFace
{
    int m_a;
    inline int meth() override { return m_a; }
};

struct Impl2 : public iFace
{
    int m_a, m_b;
    inline int meth() override { return m_a + m_b; }
};

struct PolyOp
{
    //this is only required for inferring the return-type expected by the interface
    int operator()(iFace&);
    inline int operator()(Impl1& impl1)
    {
        return impl1.meth();
    }

    //template impl because default, specific instances become overrides
    template<typename Impl>
    inline int operator()(Impl& impl2)
    {
        return impl2.meth() - impl2.m_b;
    }
};
TEST(Basic, HybridPolyContainer)
{
    Impl2 impl;
    std::tie(impl.m_a, impl.m_b) = std::pair{3, 2};
    iFace* ref = &impl;
    ImplRef< iFace, Impl1, 0 > ir1(ref);
    ImplRef< iFace, Impl2, 0 > ir2(ref);
    assert(ir1.instance() == -1);
    assert(ir2.instance() == 0);
    OpaqueImplCollector< iFace, Impl2, Impl1 > implContainer(ref);
    assert(implContainer.m_idx == 1);
    PolyOp polyop;
    assert(implContainer.apply(polyop) == 3); // impl.m_a);
};
struct iFace
{
    virtual int meth() = 0;
};

struct Impl1 : public iFace
{
    int m_a;
    inline int meth() override { return m_a; }
};

struct Impl2 : public iFace
{
    int m_a, m_b;
    inline int meth() override { return m_a + m_b; }
};

struct PolyOp
{
    //this is only required for inferring the return-type expected by the interface
    int operator()(iFace&);
    inline int operator()(Impl1& impl1)
    {
        return impl1.meth();
    }

    //template impl because default, specific instances become overrides
    template<typename Impl>
    inline int operator()(Impl& impl2)
    {
        return impl2.meth() - impl2.m_b;
    }
};
TEST(Basic, HybridPolyContainer)
{
    Impl2 impl;
    std::tie(impl.m_a, impl.m_b) = std::pair{3, 2};
    iFace* ref = &impl;
    ImplRef< iFace, Impl1, 0 > ir1(ref);
    ImplRef< iFace, Impl2, 0 > ir2(ref);
    assert(ir1.instance() == -1);
    assert(ir2.instance() == 0);
    OpaqueImplCollector< iFace, Impl2, Impl1 > implContainer(ref);
    assert(implContainer.m_idx == 1);
    PolyOp polyop;
    assert(implContainer.apply(polyop) == 3); // impl.m_a);
    
    Impl1 implOne;
    implOne.m_a = 7;
    ref = &implOne;
    OpaqueImplCollector< iFace, Impl2, Impl1 > implContainerOne(ref);
    assert(implContainerOne.m_idx == 0);
    // does not access template operator, access specific overload
    assert(implContainerOne.apply(polyop) == 7);

};
added 1463 characters in body
Source Link

One-time dynamic, many-time *almost* static type dispatch

  1. making a couple comparisons with an integer in the stack can be slightly faster sometimes that walking to a vtable, which would pay-off if the object methods are called many times
  2. the tradeoff of bigger stack space occupied by the extra-pointers and the extra-comparisons instead of a parametrized Duff's device jump is unavoidable without compiler support of variadic parameter pack switch folds (not totally true, see final remarks)

Final remarks

Although lack of a switch fold expression makes life a bit harder, it's still possible to destructure several variadic specializations in order to provide switch-based apply implementations:

template <typename Interface, typename ImplFirst, typename ImplLast>
struct OpaqueImplCollector< Interface, ImplFirst, ImplLast> : public ImplRef<Interface, ImplLast, 0>,
                                                              public ImplRef<Interface, ImplFirst, 1>
{
    using BaseImplRef0 = ImplRef<Interface, ImplLast, 0>;
    using BaseImplRef1 = ImplRef<Interface, ImplFirst, 1>;
    static constexpr int level = 1;
    const int m_idx;

    //template<typename >
    OpaqueImplCollector(Interface* i) : BaseImplRef0(i), BaseImplRef1(i),
                                        m_idx( (BaseImplRef1(i).instance() > -1) ? level : BaseImplRef0(i).instance() )
    {}

    inline int instance() const
    {
        return m_idx;
    }

    template<typename Functor>
    decltype(std::declval<Functor>()(std::declval<Interface&>())) apply(Functor f)
    {
        assert(m_idx > -1);
        switch( m_idx)
        {
            case 0:
            return BaseImplRef0::apply(f);
            case 1:
            return BaseImplRef1::apply(f);
            default:
            assert(m_idx > -1);
        }
    }
};

One-time dynamic, many-time static type dispatch

  1. making a couple comparisons with an integer in the stack can be slightly faster sometimes that walking to a vtable, which would pay-off if the object methods are called many times
  2. the tradeoff of bigger stack space occupied by the extra-pointers and the extra-comparisons instead of a parametrized Duff's device jump is unavoidable without compiler support of variadic parameter pack switch folds

One-time dynamic, many-time *almost* static type dispatch

  1. making a couple comparisons with an integer in the stack can be slightly faster sometimes that walking to a vtable, which would pay-off if the object methods are called many times
  2. the tradeoff of bigger stack space occupied by the extra-pointers and the extra-comparisons instead of a parametrized Duff's device jump is unavoidable without compiler support of variadic parameter pack switch folds (not totally true, see final remarks)

Final remarks

Although lack of a switch fold expression makes life a bit harder, it's still possible to destructure several variadic specializations in order to provide switch-based apply implementations:

template <typename Interface, typename ImplFirst, typename ImplLast>
struct OpaqueImplCollector< Interface, ImplFirst, ImplLast> : public ImplRef<Interface, ImplLast, 0>,
                                                              public ImplRef<Interface, ImplFirst, 1>
{
    using BaseImplRef0 = ImplRef<Interface, ImplLast, 0>;
    using BaseImplRef1 = ImplRef<Interface, ImplFirst, 1>;
    static constexpr int level = 1;
    const int m_idx;

    //template<typename >
    OpaqueImplCollector(Interface* i) : BaseImplRef0(i), BaseImplRef1(i),
                                        m_idx( (BaseImplRef1(i).instance() > -1) ? level : BaseImplRef0(i).instance() )
    {}

    inline int instance() const
    {
        return m_idx;
    }

    template<typename Functor>
    decltype(std::declval<Functor>()(std::declval<Interface&>())) apply(Functor f)
    {
        assert(m_idx > -1);
        switch( m_idx)
        {
            case 0:
            return BaseImplRef0::apply(f);
            case 1:
            return BaseImplRef1::apply(f);
            default:
            assert(m_idx > -1);
        }
    }
};
added 136 characters in body
Source Link
Loading
Source Link
Loading