Class Template Argument Deduction Not Working with Alias Template

Class template argument deduction not working with alias template

This was a feature that we considered when formulating the proposal, but it was eventually cut from the C++17 feature set because we didn't yet have a good enough design for it. In particular, there are some subtleties regarding how you select and transform deduction guides from the aliased template into deduction guides for the alias template. There are also open questions as to how to behave if the alias template is not a simple alias for another template. Some examples:

template<typename T> struct Q { Q(T); };     // #1
template<typename T> struct Q<T*> { Q(T); }; // #2
template<typename U> using QP = Q<U*>;
int *x;
Q p = x; // deduces Q<int*> using #1, ill-formed
QP q = x; // deduces Q<int*> using #1, or
// deduces Q<int**> using #2?

template<typename T> Q(T) -> Q<T>; // #3
QP r = x; // can we use deduction guide #3 here?

template<typename T> Q(T*) -> Q<T**>; // #4
int **y;
QP s = y; // can we use deduction guide #4 here?

template<typename T> struct A { typedef T type; struct Y {}; };
template<typename T> using X = typename A<T>::type;
template<typename T> using Y = typename A<T>::Y;
X x = 4; // can this deduce T == int?
Y y = A<int>::Y(); // can this deduce T == int?

There are decent answers to the above questions, but tackling them adds complexity, and it seemed preferable to disallow deduction for alias templates for C++17 rather than rush something flawed in.

Update [C++20]: This topic was revisited for C++20, and we approved P1814R0, which permits class template argument deduction for alias templates.

The original example is now valid. For the examples above:

  • CTAD still only considers constructors from the primary template. So in QP q = x;, #2 is not considered, and instead #1's constructor is used. That constructor is implicitly converted into a guide for Q:

    template<typename T> Q(T) -> Q<T>;

    which is then converted into a guide for the alias template QP by deducing the right-hand side of the guide for Q (Q<T>) from the right-hand side of the alias template (Q<U*>), which deduces T = U*, then substituting that back into the guide, thereby producing the equivalent of:

    template<typename U> Q(U*) -> Q<U*>;
    // ... which is effectively ...
    template<typename U> QP(U*) -> QP<U>;
    // ... except that explicit deduction guides are not
    // permitted for alias templates

    That guide is then used to deduce the type of q, which deduces U = int, so the type of q is Q<int*>, so the initialization of q is ill-formed.

  • The initialization of r does consider deduction guide #3, which is transformed into a guide for QP as described above

  • The initialization of s does consider deduction guide #4; deducing Q<T**> from Q<U*> deduces nothing, so we retain the deduction guide

    template<typename T> Q(T*) -> Q<T**>;

    as-is, but add a constraint that the result of deduction must match the form of QP. We then deduce T = int, substitute that in to compute a result type of Q<int**>, and check that we can deduce QP<U> from Q<int**>, which we can. So the type of s is deduced as Q<int**>.

  • CTAD for alias templates is only supported where the right-hand side of the alias template is a simple-template-id (of the form maybe::stuff::templatename<args>). So neither X nor Y is deducible.

Can alias templates have default template parameters?

C++20 introduces CTAD for alias templates

An alias template may indeed have default template arguments, and it is legal to have default template arguments to template parameters that are followed by a template parameter pack, as per [temp.param]/14:

If a template-parameter of a class template, variable template, or alias template has a default template-argument, each subsequent template-parameter shall either have a default template-argument supplied or be a template parameter pack. [...]

The default template argument is a red herring, however, and the key here is whether class template argument deduction is valid or not for alias templates, and we may minimize your example to the following one:

#include <tuple>

template <typename T>
using tpl = std::tuple<T>;

tpl x{1}; // should deduce tpl<int> in C++20
// Clang: error
// GCC 9.3: error
// GCC 10.1: ICE / internal compiler error

As per P1814R0(1), which was accepted for C++20, the minimal example above is indeed legal, but Clang is yet to implement P1814R0, explaining why Clang rejects it. GCC, on the other hand, lists P1814R0 as implemented for GCC 10, meaning it should accept it for C++20.

(1) As per C++20 and P1814R0 (Wording for Class Template Argument Deduction for Alias Templates), (/wording for original proposal P1021R4) CTAD is applicable also for alias templates, whilst however not allowing explicit deduction guides for them.

In C++17 you need to include the template argument list (even if it's empty) when using alias templates - there is no equivalent to class template argument deduction for alias templates in C++17:

#include <tuple>

template <typename T = int, typename... Ts>
using tpl = std::tuple<T, Ts...>;

tpl<> x; // OK in GCC and Clang

An ICE (internal compiler error) is always a bug, no matter if the code is ill-formed or well-formed, and as noted above GCC emits an ICE only for 10.1 and later, whereas it yields an error for previous releases.

Thus, GCC apparently have a ICE regression for 10.1 (which was suspiciously listed as the target when CTAD for alias templates were implemented). It is at the very least related to the following bug report:

  • Bug 96199 - [10/11 Regression] internal compiler error: in tsubst_copy with CTAD for alias templates

Which however is listed as resolved, whereas your example still yields an ICE for a GCC trunk that includes the fix to 96199.


We may finally note that GCC successfully applies CTAD for the alias template where we only use a template parameter pack:

#include <tuple>

template <typename... Ts>
using tpl = std::tuple<Ts...>;

tpl x{1}; // OK

but that if we replace std::tuple by std::vector in the minimal example:

#include <vector>

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

vec x{{1}}; // GCC 10.1: ICE

we get another kind of ICE for GCC 10.1 (and forward), whereas adding a default template argument and replacing the braced-direct-initialization with default-initialization is accepted.

#include <vector>

template <typename T = int>
using vec = std::vector<T>;

vec x; // GCC 10.1: OK

C++ Template Argument Deduction with Additional Specified Template Arguments

Unfortunately, "Class template argument deduction is only performed if no template argument list is present. If a template argument list is specified, deduction does not take place." - https://en.cppreference.com/w/cpp/language/class_template_argument_deduction

You could use a wrapper function to do what you want, but it won't work with the constructor itself.

Making guides for function template argument deduction in C++

OK I got the answer actually functions can't have deduction guides. It only works with class templates. Thanks for pointing me to the right direction.

Why is the template argument deduction not working here?

Just as first note, typename name is used when you mention a dependent name. So you don't need it here.


template <class T>
struct S
{
typedef T& type;
};

Regarding the template instantiation, the problem is that typename S<A>::type characterizes a nondeduced context for A. When a template parameter is used only in a nondeduced context (the case for A in your functions) it's not taken into consideration for template argument deduction. The details are at section 14.8.2.4 of the C++ Standard (2003).

To make your call work, you need to explicitly specify the type:


temp<char>(c);

variadic template deduction with two template argument fails

If Dims should equal the number of parameters passed, you should use the sizeof... operator instead of letting the caller specify the size.


CTAD (class template argument deduction) only works when all template arguments of the class are deduced.

The workaround is to let all arguments be deduced. You can wrap the class that deduces the indices into another one that can have the size given explicitly:

#include <iostream>
#include <tuple>

template <size_t Dims>
struct wrapper {
template <std::same_as<char> ... Indices>
struct MIndices {
std::tuple<Indices...> indices;
MIndices() = delete;
constexpr explicit MIndices(Indices... args) : indices(args...) {
}
};
};

int main() {
wrapper<4>::MIndices myI('i', 'j', 'l', 'z');
}

Live Demo

Type aliases and template template argument deduction

Yes, that is intentional. Alias templates are indeed, as you said, somewhat of a "second class citizen". To start with, alias templates cannot be specialized, that's a real big hint right there.

Now, their "lower grade" as evident in your example is all about [temp.alias]/2:

When a template-id refers to the specialization of an alias
template, it is equivalent to the associated type obtained by
substitution of its template-arguments for the template-parameters
in the type-id of the alias template. [ Note: An alias template name
is never deduced. — end note ]

The above means, that when you write Matched<Alias<int>>, since Alias<int> refers to a specialization of the alias template, it's equivalent to directly writing Matched<std::tuple<int,int>>. And it's quite obvious why that doesn't match the specialized variable template.

It's not an oversight, nor is it going to be fixed. Alias templates are there to provide a shorthand for more complex template expressions. And you wouldn't want the wrong overload to be called, or the wrong template specialization instantiated, because you used a shorthand instead of the whole complex expression.

Deduction guide with type alias after -

Clang and ICC are correct here: the restriction on how to name the template and type is phrased syntactically. C++20 allows “class template” argument deduction for alias templates, but does not support (separate) deduction guides for them.



Related Topics



Leave a reply



Submit