James
James

Reputation: 183

Multiple Variadic Parameter Pack for Template Class

I am using variadic parameter packs for policy based class design.

template <APITypes APIType, class... Policies>
class IShader : public Policies... {

};

Policies are defined when called or with defaults if none are specified. The problem comes when I need to add another variadic parameter pack:

template <AttributeType... Attributes, APITypes APIType, class... Policies>
class IShader : public Policies... {

};

This results in the error "Template parameter pack must be the last template parameter". I am planning to use the attribute pack to change the behaviour of at least one of the policies. But I can't work out how to get two variadic parameter packs in one template class.

Upvotes: 13

Views: 5466

Answers (3)

memtha
memtha

Reputation: 864

Make a nested class, with each layer having one variadic pack. In essence:

template<class... ArgsA> class Wrapper {
public:
    template<class... ArgsB> class Type {
        //Here you have access to both template packs
        //...
    }
}
//Use like this:
Wrapper<int, char, long unsigned int>::Type<float, double> a;
//...

Define any function of Type outside both classes, but inside the header, or gcc will get confused.

For a more complete use case, see my answer here

Upvotes: 0

md5i
md5i

Reputation: 3083

I think the simplest answer is to create template type wrappers for your parameter packs. For example:

template <AttributeType... T>
struct Attributes {};

template <typename... T>
struct Policies {};

Then you can declare your IShader type:

template <typename... T>
class IShader;

Create your implementation as a specialization. Note that in a specialization, you can have multiple parameter pack arguments.

template <AttributeType... AttributeList, ApiTypes APIType, typename... PolicyList>
class IShader<Attributes<AttributeList...>, ApiType, Policies<PolicyList...>> 
    : public PolicyList...
{
    ...
};

Then you can even allow the user to specify the arguments in different orders (make sure you forward the constructors if doing it this way via inheritance):

template <AttributeType... AttributeList, ApiTypes APIType, typename... PolicyList>
struct IShader<ApiType, Policies<PolicyList...>, Attributes<AttributeList...>
    : public IShader<Attributes<AttributeList...>, ApiType, Policies<PolicyList...>>
{
    using IShader<Attributes<AttributeList...>, ApiType, Policies<PolicyList...>>::IShader;
};

If you're being really fancy, you can even use metaprogramming tricks to allow the arguments in any order without enumerating all orders. This is left as an exercise to the reader. :)

Upvotes: 14

Sam Varshavchik
Sam Varshavchik

Reputation: 118292

In the discussion comments you expressed a willingness to consider some kind of indirection, or "a wrapper of some kind for the attribute list".

A lightweight std::tuple-based wrapper, together with specialization, might work here:

template <typename attribute_tuple, APITypes APIType,
          typename policy_tuple> class IShader;

template <AttributeType... Attributes, APITypes APIType,
          class... Policies>
class IShader<std::tuple<Attributes...>, APIType,
              std::tuple<Policies...>> : public Policies... {

// ...

};

The goal here is to use a template instance along the lines of:

IShared<std::tuple<Attribute1, Attribute2>, APITypeFoo,
        std::tuple<Policy1, Policy2>> ishared_instance;

And cross your fingers that this is going to match the specialized template declaration, at which point both parameter packs are available for the template specialization to use, individually.

Upvotes: 7

Related Questions