Can template specialisation be avoided in this case?

Viewed 79

I recently came across the following:

// declare
template <class> struct A;
// specialise
template <template <class...> class C, class... Fields> struct A<C<Fields...>> {
  template <typename... Args> explicit A(C<Fields...>* c, Args&&... args) {}
};
// instantiate
template <template <class...> class C, class... Fields, class... Args>
A<C<Fields...>> MakeA(C<Fields...>* c, Args&&... args) {
  return A<C<Fields...>>(c, std::forward<Args>(args)...);
}

If I understand it correctly (comments above are mine), it is declaring A as a template, then specialising A, and the rest is more or less straightforward.

Why do we need to do this instead of being able to directly define the template, something like the following (which compiles and seems to be otherwise functional as far as I can tell):

template <template <typename...> class C, typename... Fields> struct A1 {
 public:
  template <typename... Args> explicit A1(C<Fields...>* c, Args&&... args) {}
};

template <template <typename...> class C, typename... Fields, typename... Args>
A1<C, Fields...> MakeA1(C<Fields...>* c, Args&&... args) {
  return A1<C, Fields...>(c, std::forward<Args>(args)...);
}

My question is both approaches are equally valid/acceptable/technically correct? If yes, is one preferable for some subtle technical or aesthetic reasons and why?

For additional context, I did not see any other specialisations in the original code so I'm not sure what the code with the specialisation buys us.

1 Answers

Their usage syntax is different:

first snippet would be

A<std::vector<int>> a(&some_vector, 42, 51);

whereas the second would be

A<std::vector, int> a(&some_vector, 42, 51);

Which is indeed mitigated with MakeA.

But, difference might be important in template, where you would need to "propagate" the different signature:

template <typename T>
void foo(T *t)
{
    A<T> a(t);
    // ...
}

versus

template <template <typename> class C,  typename... Ts>
void foo(C<Ts...> *t)
{
    A<C, Ts...> a(t);
    // ...
}

Additional caveat is for specialization and template aliases:

template <typename T>
using MyVec = std::vector<T>;

Whereas MyVec<T> is same as std::vector<T>, MyVec is not the same as std::vector

so

struct Specialization<A<std::vector<int>>> {/*..*/};

would be fine

whereas

struct Specialization<A<std::vector, int>> {/*..*/};

would be more problematic for A<MyVec, int>.

Related