12

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.

James
  • 173
  • 3
  • 12
  • 1
    You can't. You need to rethink your template/class design. – Sam Varshavchik May 13 '16 at 02:41
  • 1
    Not even with some kind of indirection? Say a wrapper of some kind for the attribute list. Or templates of templates? – James May 13 '16 at 02:42
  • 6
    You could always consider something like `IShader, APIType, Policies>` – chris May 13 '16 at 02:42
  • Sort of like how you can't define `void foo(int numAttributes, ..., int numPolicies, ...);` – user253751 May 13 '16 at 02:47
  • @chris Is that how it would look when called? I tried something similar using a wrapper around the attributes but could not work out how to then either put said wrapper in the template or make the list accessible to the policy that needs it. – James May 13 '16 at 02:47
  • @James, Yes, similar to the now-provided answer – chris May 13 '16 at 02:56
  • Possible duplicate of [Parameter pack must be at the end of the parameter list... When and why?](http://stackoverflow.com/questions/34940875/parameter-pack-must-be-at-the-end-of-the-parameter-list-when-and-why) – skypjack May 13 '16 at 05:56

3 Answers3

14

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. :)

md5i
  • 3,018
  • 1
  • 18
  • 32
7

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.

Sam Varshavchik
  • 114,536
  • 5
  • 94
  • 148
  • Are the tuples needed when declaring objects of the IShader class? – James May 13 '16 at 03:05
  • @James The tuples are needed, but only as a type. No tuples are actually instantiated. – md5i May 13 '16 at 03:09
  • Even here `IShared, APITypeFoo, std::tuple> ishared_instance;`? Won't it then not use the specialization that provides the implementation, but rather the default. Could you not use the class like this `IShared ishared_instance;`? – James May 13 '16 at 03:15
  • @James Your first succeeds because the implementation is the most-specialized matching specialization. Your latter example would fail. The template is of three arguments. attribute_tuple gets the first, APIType gets the second, policy_tuple gets the third. If you don't like the look of the tuples, create your own type wrappers like in my solution in the other answer. – md5i May 13 '16 at 03:21
  • So the specialization does not pattern match on the template argument, i.e. `template ` but rather matches this part `class IShader, APIType, std::tuple>`. – James May 13 '16 at 03:23
0

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

memtha
  • 797
  • 5
  • 24