How to write deduction guidelines for aliases of aggregate templates?

402 views Asked by At

With C++20, it is possible to have deduction guidelines generated for an alias template (See section "Deduction for alias templates" at https://en.cppreference.com/w/cpp/language/class_template_argument_deduction). Yet, I could not make them work with aggregate initialization syntax. It looks like in this case the deduction guidelines for the alias is not generated.

See this example:

#include <array>

template <size_t N>
using mytype = std::array<int, N>;

// Deduction guideline ???

int main() {
    // mytype error_object = {1, 4, 7}; // ERROR
    mytype<3> object = {1, 4, 7}; // OK, but I have to manually specify the size.
    return object[0];
}

I have tried writing deduction guidelines but I get compiler errors every time.

template <typename T, typename ... U>
mytype(T, U...) -> mytype<1+sizeof...(U)>; // Compiler error

and any other guideline I could think of.

Is it even possible to have the size of the array alias automatically deduced?

I am using GCC 10.2

1

There are 1 answers

0
cpplearner On BEST ANSWER

Is it even possible to have the size of the array alias automatically deduced?

I believe that it should be possible with a standard-conforming implementation. You needn't (and can't) add any more guides.

However, GCC implements a different set of rules than what the standard specifies:

This implementation differs from [the specification] in two significant ways:

1) We include all template parameters of A, not just some.
2) The added constraint is same_type instead of deducible.

The implementer believed that "this simplification should have the same effect for real uses". But apparently this is not the case: this implementation fails to work in your case and ICEs in some other cases.


For the reference, I'll try to follow the standard and show how the guide for mytype is generated.

We have this alias template declaration (the alias template is called A in the standard):

template <size_t N>
using mytype = std::array<int, N>;

and this deduction guide from the standard library ([array.cons]):

template<class T, class... U>
array(T, U...) -> array<T, 1 + sizeof...(U)>;

First, a function template (called f in the standard) is generated from the deduction guide ([over.match.class.deduct]/1):

template<class T, class... U>
auto f(T, U...) -> array<T, 1 + sizeof...(U)>;

Then, per [over.match.class.deduct]/2:

the template arguments of the return type of f are deduced from the defining-type-id of A according to the process in [temp.deduct.type] with the exception that deduction does not fail if not all template arguments are deduced.

That is, we deduce the template arguments in array<T, 1 + sizeof...(U)> from std::array<int, N>. In this process, T is deduced to be int; U is not deducible, so it is left as-is.

The result of deduction is substituted into the function template, resulting in:

template<class T, class... U>
auto g(int, U...) -> array<int, 1 + sizeof...(U)>;

Then, we generate a function template f'. f' has the same return type and function parameter types as g. (If f has special properties, they are inherited by f'.) But notably, the template parameter list of f' consists of ([over.match.class.deduct]/(2.2), emphasis mine):

all the template parameters of A (including their default template arguments) that appear in the above deductions or (recursively) in their default template arguments, followed by the template parameters of f that were not deduced (including their default template arguments), otherwise f' is not a function template.

Since N does not appear in the deduction, it is not included in the template parameter list (this is where GCC differs from the standard).

Additionally, f' has a constraint ([over.match.class.deduct]/(2.3)):

that is satisfied if and only if the arguments of A are deducible (see below) from the return type.

Therefore, according to the standard, the generated function template looks like:

template<class... U>
  requires deducible<array<int, 1 + sizeof...(U)>>
auto f'(int, U...) -> array<int, 1 + sizeof...(U)>;

Clearly, the size can be deduced as 1 + sizeof...(U) according to this guide.

In the next step, let's see how deducible is defined.

[over.match.class.deduct]/3:

The arguments of a template A are said to be deducible from a type T if, given a class template

template <typename> class AA;

with a single partial specialization whose template parameter list is that of A and whose template argument list is a specialization of A with the template argument list of A ([temp.dep.type]), AA<T> matches the partial specialization.

In our case, the partial specialization would be:

 template <size_t N> class AA<mytype<N>> {};

So deducible can be declared as:

 template <class T> concept deducible = requires { sizeof(AA<T>); };

Since N is deducible from 1 + sizeof...(U), array<int, 1 + sizeof...(U)> is always a valid match for mytype<N> (a.k.a. std::arrray<int, N>), and thus the constraint deducible<array<int, 1 + sizeof...(U)>> is always satisfied.

Therefore, according to the standard, the generated guide is viable and can deduce the size.

In comparison, GCC generates:

template<class... U, size_t N>
  requires same_type<array<int, 1 + sizeof...(U)>, mytype<N>>
auto f_(int, U...) -> array<int, 1 + sizeof...(U)>;

... which is not able to deduce N.