Template specialization and references
Asked Answered
M

1

8

In Functional programming in C++, chapter 11 deals with some basic template meta programming.

In this context, the author shows this implementation of remove_reference/remove_reference_t, which are essentially the same as those described on cppreference.

template<typename T> struct remove_reference      { using type = T; };
template<typename T> struct remove_reference<T&>  { using type = T; };
template<typename T> struct remove_reference<T&&> { using type = T; };
template<typename T> using remove_reference_t = typename remove_reference<T>::type;

With reference to the code above, the author comments that when "calling" remove_reference_t<int>, only the general (or primary? What is the correcto word here?) template successfully substitutes T, and the other two fail. This is clear to me, there's no way int can be written as/matched against T& or T&&.

As regards remove_reference_t<int&>, however, the author says that the second specialization cannot match. Well, couldn't it be a match thanks to reference collapsing? I mean, can't T&& match int& if I substitute T for int&, thus getting int&&& == int&?

Similarly, when calling remove_reference_t<int&&>, can't the first specialization's T& match int&& if T is substituted for int&? (Why in the world did I think that & & would collapse to && instead of &?)

What makes the compiler discard one specialization?

Mason answered 8/9, 2020 at 21:7 Comment(0)
K
5

only the general (or primary? What is the correcto word here?) template

The technical term used by the C++ Standard is "primary class template". It will also be the most general class template, compared to its partial specializations and explicit specializations. So that could also be a reasonable thing to call it, given enough context.

The "reference collapsing rule" is found in [dcl.ref]/6 and applies mainly when determining the meaning of combining a specific type name which aliases a reference type with a & or && token which would normally form a reference to the type name's type. Deducing template arguments for a template parameter of the form T& or T&& is sort of the reverse of that. Although it's helpful to think of template argument deduction as "find the template arguments so that the resulting types match up", the technical details of template argument deduction are much more specific; [temp.deduct] is several pages of rules for exactly how this deduction proceeds, and there are additional relevant rules in other sections. The detail is needed so compilers agree on cases when there could otherwise be more than one "correct" answer, and so that compilers aren't required to deal with some of the more difficult cases.

In particular, when matching a dependent type P with a known type A, by the list of deducible types in [temp.deduct.type]/8, deduction can occur if both P and A have the form T& or if both have the form T&&. When attempting argument deduction for the partial specialization remove_reference<T&&> to determine the definition of remove_reference<int&>, P is T&& and A is int&, so they do not share one of these forms.

The template argument deduction rules do not have a general allowance for deducing arguments from a reverse of the reference collapsing rule. But they do have a limited allowance which is related for certain cases: Per [temp.deduct.call]/3, if T is a template type parameter, but not a parameter for a class template, then the type T&& is a forwarding reference. When comparing types for argument deduction, if P=T&& is a forwarding reference type and A is an lvalue reference type, then the template type parameter T can be deduced as the lvalue reference type A, only if A is the type of an lvalue function argument expression ([temp.deduct.call]/3 again) or sometimes if P and A are being compared because they represent function parameter types within two compared function types ([temp.deduct.type]/10).

Similarly, when ["]calling["] remove_reference_t<int&&>, can't the first specialization's T& match int&& if T is substituted for T&?

In this case, there's no possible way that the partial specialization remove_reference<T&> can match remove_reference<int&&>. Even if the process of template argument deduction allowed finding a potential answer for this case, there is no possible type T such that T& is the same as int&&.

Kittie answered 8/9, 2020 at 22:27 Comment(7)
Does the phrase a specific type name which aliases a reference type in the second paragraph refer to anything like char&&, std::string&, int*&, but not T&/T&& when T is a template parameter? There are still some bits not clear to me, especially in the 4th paragraph (the 3rd paragraph starting with The). I'll comment again this evening to be more specific about what's not clear to me.Mason
One simple case without templates involved would be using X = int&; using Y = X&&;. X is a type name, and it aliases the reference type int&. So the syntax X&& involves applying the reference collapsing rule to get that Y names int&. Template type parameters are also type names, and during instantiation they alias some known type. So template <typename X> using Z=X&&; using Y=Z<int&>; is also using reference collapsing, again getting Y names int&.Kittie
Finally, when template argument deduction is involved, after template arguments are deduced, those template arguments are substituted for uses of the template parameters. That substitution of deduced template arguments can also involve reference collapsing just like for explicitly specified template arguments.Kittie
I think I understand what you wrote in the comments, however I haven't grasped your answer yet. I thought template type deduction is what happens to a template function (or template class from C++17, if it provides an "appropriate" constructor) when I provide it with some arguments without specifying a template argument; the template parameter is then deduced and substituted in every usage in the function definition. In your answer you refer to template type deduction with reference to when I do provide something in th <...>. This confuses me a bit. Could you help clarify this point?Mason
Those are a couple of the common cases when template argument deduction is used, but there are really a few more. One of them is this case of matching partial specializations: Suppose we have template <LIST_P> class C { DEF1 }; template <LIST_Q> class C<LIST_R> { DEF2 }; using T = C<LIST_S>;. To determine whether specialization C<LIST_S> matches the partial specialization and uses DEF2 or doesn't and uses DEF1, the compiler tries to deduce the parameters in LIST_Q so that LIST_R matches LIST_S. ...Kittie
... This partial specialization matching test is essentially the same as a deduction given template <LIST_Q> void dummy_func(C<LIST_R>&); for a call expression dummy_func(std::declval<C<LIST_S>&>());.Kittie
I expect SO will soon give an option to move this discussion to a custom chat room, which would be easier.Kittie

© 2022 - 2024 — McMap. All rights reserved.