Class template argument deduction not working with alias template
Asked Answered
D

2

43

consider the code pasted below. I have defined a very simple class, for which the compiler generates an implicit deduction guide so it can be constructed without explicit template arguments. However, the template argument deduction does not work for constructing an object from a simple alias template which only forwards directly to the target class:

template< typename A, typename B >
struct Foo {
    Foo( A const &a, B const &b )
            : a_(a), b_(b)
    { }

    A a_;
    B b_;
};

template< typename A, typename B >
using Bar = Foo<A, B>;

auto foobar() {
    Foo r{1, 2};
    Bar s{3, 4};
    // ../src/geo/vector_test_unit.cpp: In function 'auto foobar()':
    // ../src/geo/vector_test_unit.cpp:16:6: error: missing template arguments before 's'
    //   Bar s{3, 4};
    //       ^
    return 1;
}

As you can see from a code comment above, g++ is giving me an error about using the aliased template without template arguments. I was hoping in such an instance that template argument deduction would be forwarded.

So, my question: Is this by express design of the current wording of the proposal for class template argument deduction? Or is this an unfinished feature or bug in the current g++ implementation of the feature? And this would be more of a question for the authors of the proposal, or for the C++ ISO Committee, but if any of them see this: Would it be desired that the final wording of the feature include enabling alias templates such as this to also have implicit guides generated for them?

I can understand that since alias templates can have any kind of template parameters it may not always be possible for the compiler to successfully deduce the target class template arguments, but in a case like this I would expect that the compiler would be able to in the same way that it can directly for the target class.

I am building with gcc built from head only a few days ago, using --std=c++1z. The complete version info is: gcc version 7.0.0 20161201 (experimental) (Homebrew gcc HEAD- --with-jit)

Dancy answered 7/12, 2016 at 1:49 Comment(0)
U
53

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.

Use answered 7/12, 2016 at 2:28 Comment(5)
I'm building up and compiling your example file one bit at a time to try to understand it incrementally. I've just compiled it using only #1, #2, and the lines int *x; Q p = x;. It gives an error, as you indicated, unless I delete #2 and re-compile. The error message from g++ is [...] error: invalid conversion from 'int*' to 'int' ... Q p = x; ... note: initializing argument 1 of 'Q<T*>::Q(T) [with T = int]' template< typename T > struct Q<T*> { Q(T); };. To me that looks like it's trying to use #2 instead of #1 as you said. What am I not understanding?Dancy
@squidbidness The line Q p = x; deduces the type of p to be Q<int*> using #1 for class template argument deduction. After picking that type, it deduces that Q<int*> should use the Q<T*> partial specialization, deducing T = int. Finally, it tries to call a Q<int*> constructor with a parameter of type int*, which doesn't work because the only constructor (other than the default/copy/move ctors) takes an int.Use
Thank you for that clarification. The part that was confusing me was that my mental model had deducing p's type and picking a template specialization as one and the same process. Pointing out that they are separate steps helps me reason it through much better.Dancy
Is there a C++20 paper on this?Wineshop
@Wineshop Yes, see the edit (if you didn't already)Kerguelen
E
3

From http://en.cppreference.com/w/cpp/language/template_argument_deduction, "Alias templates are never deduced." So, it is by design.

Experiential answered 7/12, 2016 at 2:5 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.