Search code examples
c++c++17variadic-templates

Access variadic template structure by its interface


There is a hierarchy of classes:

template<typename T>
class FeatureInterface1 {
public:
    void f1( void ) { static_cast<T&>(*this)._f1(); }
}

class Feature1 : public FeatureInterface1<Feature1> {
    /* Allow interface class to access private elements */
    friend class FeatureInterface<Feature1>;
private:
    void _f1(void) { /* Do something there */ }
}

template<typename T>
class FeatureInterface2 {
public:
    void f2( void ) { static_cast<T&>(*this)._f2(); }
}

class Feature2 : public FeatureInterface2<Feature2> {
    /* Allow interface class to access private elements */
    friend class FeatureInterface<Feature2>;
private:
    void _f2(void) { /* Do something there */ }
}

Then there is a variadic data class:

template<typename... FEATURES> class Device {};

template<typename FEATURE, typename... OTHERS>
class Device<FEATURE, OTHERS...> : public Device<OTHERS...> {
public:
    /* Contructor */
    Device(FEATURE feature, OTHERS... others)
        : Device<OTHERS...>(others...),
          m_feature( feature ) {
    }
private:
    FEATURE m_feature;
};

and finally the full featured object made at compile time:

Device<Feature1, Feature2> device;

The task is to design a get<>() function which returns the pointer to particular object using it's interface. Sample usage:

FeatureInterface1<Feature1>* ptr_f = get<FeatureInterface1<Feature1>>(device);

In other words something like get<0>, get<1> ... accessors of std::tuple but interface-class-defined rather than index-defined.

My idea is to use std::enable_if in connection with std::is_base_of ...

The inspiration was taken from https://eli.thegreenplace.net/2014/variadic-templates-in-c/

I would be very glad to whatever willing to help me. Thanks in advance!


Solution

  • This is quite simple actually with if constexpr:

    template<typename FEATURE, typename... OTHERS>
    class Device<FEATURE, OTHERS...> : public Device<OTHERS...> {
    public:
        ...
        template <typename FEATURE_INTERFACE>
        FEATURE_INTERFACE& get()
        {
            if constexpr (std::is_base_of_v<FEATURE_INTERFACE, FEATURE>)
                return m_feature;
            else
                return Device<OTHERS...>::template get<FEATURE_INTERFACE>();
        }
        ...
    };
    

    Note that you'll get a compilation error if the Device doesn't support the requested interface. However, if you want a nullptr instead it's not that hard either with an additional specialization for empty Device:

    template<>
    class Device<> {
    public:
        template <typename FEATURE_INTERFACE>
        FEATURE_INTERFACE* get()
        {
            return nullptr;
        }
    };
    

    And then just change the main implementation to return a pointer:

    template <typename FEATURE_INTERFACE>
    FEATURE_INTERFACE* get()
    {
        if constexpr (std::is_base_of_v<FEATURE_INTERFACE, FEATURE>)
            return &m_feature;
        else
            return Device<OTHERS...>::template get<FEATURE_INTERFACE>();
    }
    

    I used a member function instead of non-member because in my opinion the implementation is way simpler this way and I also personally don't like those non-member friend getters :). Also, as Red.Wave mentioned in comments, it's quite easy to make a non-member getter using the member:

    template <typename FEATURE_INTERFACE, typename... FEATURES>
    FEATURE_INTERFACE* get(Device<FEATURES...>& device)
    {
        return device.template get<FEATURE_INTERFACE>();
    }
    

    You might also want to add const-overloads to all these getters for completeness.