How can I create deduction guides for template aliases in C++20?

1.3k views Asked by At

Suppose I have a class/struct template together with an explicit deduction guide for its constructor. Let this class have two template parameters of which one can get deduced by the deduction guide, for the other it can not.

template <int Q, typename T>
struct Foo {
     template <typename F>
     Foo(F&&) { }
};
    
template <typename T>
using alias = T;
    
template <typename T>
struct alias2 { using type = T; };
    
template <int Q, typename F>
Foo(F&& f) -> Foo<Q, alias<F>>; // deduction guide, but cannot deduce Q yet
    
template <typename T>
using Bar = Foo<1, T>; // create alias that fixes Q

/* This should generate a deduction guide for Bar<T> by first 
   "copying" Foo's deduction guide, deducing from Foo<Q, alias<F>>
   and Foo<1, T> that Q=1 and T=alias<F>=F, thus generating

   <template F>
   Bar(F&&) -> Bar<1, F>;

   if this was correct syntax. */
   
int main() {
    Bar f{ 5 };
}

If I now create an alias that will explicitly specify the formerly undeducable parameter, as far as I understand, the implicitly-generated deduction guide of this alias should be able to fully deduce both template arguments (by the rules of standard template argument deduction), even if one type is undeduced in the defining class template.

But what can I do in the scenario where I do not use alias, but alias2, i.e. change the deduction guide to

template <int Q, typename F>
Foo(F&& f) -> Foo<Q, typename alias2<F>::type>;

According to the documentation, this would now introduce a non-deduced context (as the template parameter appears left to a scope operator ::), so the template argument deduction for T=F should fail (which apparently does).


Question 1: If this theory is correct, is there something I can do about it? Suppose I do not want to use a trivial identity alias, but a more complex type transformation that will ultimatively have the shape of a typename transformation<Input>::result in the deduction guide.

Question 2: Even now, my theory fails when I remove Q entirely, as the following code will be accepted (by GCC-10/11):

template <typename T>
struct Foo {
    template <typename F>
    Foo(F&&) { }
};

template <typename T>
struct alias2 { using type = T; };

template <typename F>
Foo(F&& f) -> Foo<typename alias2<F>::type>;

template <typename T>
using Bar = Foo<T>;

template <typename T>
void some(typename alias2<T>::type) { }

int main() {
    Bar f{ 5 };
}

Why is the compiler able to deduce T from F even if this is a non-deduced context?

1

There are 1 answers

1
Yakk - Adam Nevraumont On BEST ANSWER

To do what you want, C++ would have to be able to invert a Turing-complete subprogram.

Turing-complete programs are not only not-invertible, it isn't possible to determine if a given Turing-complete program is invertible or not. You can define sublanguages where they are all invertible, but those sublanguages lack Turing-complete power.

Deducing the Bar alias argument:

template <typename T>
using Bar = Foo<1, T>;

requires inverting the 2nd template argument alias<F> to find F. When alias is a trivial template alias, this is possible, permitted, and required by the C++ standard.

When alias2 evaluates is to a foo<F>::type, such a construct is capable of turing-complete computation. Rather than have compilers try to invert such a computation, the C++ standard uniformly says "don't try". It uses "dependent type" usually to block such an inversion attempt.

In your second case, Bar is a trivial alias of Foo. Foo has a deduction guide. That deduction guide tells how to get from F to T, so the compiler doesn't have to invert any potentially Turing-complete programs in order to determine T.

The C++ language has a bunch of wording to permit template aliases that are just renaming of parameters or the like to act as if they were the original type. Originally even a toy alias would block a bunch of this kind of deduction; but this was found to be a bad plan. So they added text to the standard to describe what kind of template aliases where "trivial" like that, and modified the wording of deduction rules so that they would be treated as transparent.


In order to invert an arbitrary Turing-complete program (in fact, almost any structurally non-trivial type transformation) during type deduction, you must explicitly give the inversion.

Once you have accepted that, it becomes battle with syntax, not a conceptual one.

I'm unaware of the current status of user-defined template deduction guides of alias templates. Last I heard it wasn't supported, but I haven't checked recently.