简体   繁体   中英

Recursive function acting on variadic class template parameters

So I have the following (cut down) classes:

template <typename A, typename... B>
struct ComponentTupleAccessor: 
    public ComponentArray<A>, 
    public ComponentTupleAccessor<B...> 
{
    ComponentTupleAccessor(const uint32_t capacity): 
        ComponentArray<A>(capacity), 
        ComponentTupleAccessor<B...>(capacity) 
    {}
};
template <typename A>
struct ComponentTupleAccessor<A>: 
    public ComponentArray<A> 
{
    ComponentTupleAccessor<A>(const uint32_t capacity): 
        ComponentArray<A>(capacity) 
    {}
};
template <typename A, typename ...B>
class ComponentTuple {
    ComponentTupleAccessor<A, B...> m_Components;
    uint32_t m_Capacity;

public:
    ComponentTuple(const RB32u capacity): 
        m_Capacity{capacity}, 
        m_Components(capacity) 
    {}

    template <typename S, typename ...T>
    void pop_back() {
        m_Components.Component<S>::pop_back();
        pop_back<T...>();
    }

    template <typename S>
    void pop_back() {
        m_Components.Component<S>::pop_back();
    }

    void pop_back() {
        pop_back<A, B...>();
    }
};

The ComponentArray class is basically a wrapper around a vector that holds a bunch of components of a particular type.

The ComponentBlockTupleAccessor class more or less emulates a cut down version of std::tuple where the any number of unique types of ComponentArray can be inherited into the class ComponentTuple using the variadic templates.

The pop_back function in ComponentTuple is designed to recursively pop_back an element off each of the ComponentArray s.

Outside of the ComponentTuple class I'd like to be able to simply call something like compTupleInstance.pop_back() and all ComponentArray 's should have their last elements removed.

I get a compile error "call of overloaded 'pop_back()' is ambiguous" pop_back();

I can't seem to figure out a combination of the A, B (pack), S, and T (pack) template parameters that gives me the functionality I need. What am I missing here?

Edit: Here is a simple usage scenario:

// ComponentTuple contains an int and a float ComponentArray with capacity 8.
ComponentTuple<int, float> dut(8);

// Push a set of new components to the ComponentArrays.
// This function has a similar structure to that of pop_back.
dut.push_back({8}, {3.141f});
// Another one
dut.push_back({4}, {2.718f});

// Remove the last element from all of the ComponentArrays.
dut.pop_back();

ComponentTuple template parameters will always be unique types, and there will always be greater than one.

A copy from the question:

    template <typename S, typename ...T>  // T can be empty
    void pop_back() {
        m_Components.Component<S>::pop_back();
        pop_back<T...>();
    }

    template <typename S>
    void pop_back() {
        m_Components.Component<S>::pop_back();
    }

If I invoke pop_back<A>() I have S = A . But, am I calling the first method with T empty, or am I calling the second method?

The core issue: template <typename S, typename... T> and template <typename S> look equally good for the compiler when there is only one template argument (the pack can be empty). It cannot make a decision on which overload to use.

Solution: You can use fold expression (c++17 or above).

void pop_back() {
    (m_Components.ComponentArray<A>::pop_back(), ... , m_Components.ComponentArray<B>::pop_back());
}

...Also the code breaks (even with the fold expression above) if used like this: ComponentTuple<int, int, double> (ambiguous base class).

Thanks for your help guys, the ambiguity between <typename S> and <typename S, typename... T> seems obvious now that you've pointed it out. It seems it's just not possible to do it the way I was trying to, for that reason.

I ended up using a if constexpr to test if the recursion is at the last Type, so I can terminate the recursion at that point. I like this even better since in the non templated pop_back there is no risk of giving template parameters that weren't used in declaring the class. eg

ComponentTuple<int, float> dut(8);

push_back<Banana, char>; // Not int, float so should cause compile error.

Using the constexpr method, I can privatise the pop_back function (see pop_back_ below), and only expose the no-params version so that the method can only be called in the correct way:

template <typename A, typename ...B>
class ComponentTuple {
    ComponentTupleAccessor<A, B...> m_Components;
    uint32_t m_Capacity;

    template <typename S, typename... T>
    void pop_back_() {
        m_Components.ComponentBlock<S>::pop_back();

        if constexpr (sizeof...(T) > 0) {
            pop_back_<T...>();
        }
    }

public:
    ComponentTuple(const RB32u capacity): 
        m_Capacity{capacity}, 
        m_Components(capacity) 
    {}

    void pop_back() {
        pop_back_<A, B...>();
    }
};

Obviously I need to make sure I'm using at least c++17 to use if constexpr .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM