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?

Enlico
  • 23,259
  • 6
  • 48
  • 102

1 Answers1

4

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&&.

aschepler
  • 70,891
  • 9
  • 107
  • 161
  • 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. – Enlico Sep 09 '20 at 07:03
  • 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 using Z=X&&; using Y=Z;` is also using reference collapsing, again getting `Y` names `int&`. – aschepler Sep 10 '20 at 04:08
  • 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. – aschepler Sep 10 '20 at 04:12
  • 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? – Enlico Sep 10 '20 at 06:55
  • 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 class C { DEF1 }; template class C { DEF2 }; using T = C;`. To determine whether specialization `C` 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`. ... – aschepler Sep 10 '20 at 12:05
  • ... This partial specialization matching test is essentially the same as a deduction given `template void dummy_func(C&);` for a call expression `dummy_func(std::declval&>());`. – aschepler Sep 10 '20 at 12:11
  • I expect SO will soon give an option to move this discussion to a custom chat room, which would be easier. – aschepler Sep 10 '20 at 12:29