16

Consider the following snippet below:

template <class T>
using identity = T;

template <class T>
void foo(identity<T>&&) {}

int main()
{
  int i{};
  foo(i);
}

i is an lvalue, hence if foo declares a forwarding reference parameter, it should compile. However, if identity<T>&& is turned to be int&&, it should raise an error instead.

The code compiles in GCC 6.0.0 (demo).

The code fails to compile in Clang 3.7.0 (demo) with error message:

error: no known conversion from 'int' 
to 'identity<int> &&' (aka 'int &&') for 1st argument

Which one is right?

Tomas Kubes
  • 23,880
  • 18
  • 111
  • 148
Marc Andreson
  • 3,405
  • 5
  • 35
  • 51
  • 1
    Funny thing, `identity` seems to work in both GCC and Clang ([demo](http://melpon.org/wandbox/permlink/r1YH40UcY8lwouQ4)) being treated as a forwarding reference – Marc Andreson Apr 24 '15 at 16:08
  • That's probably because `T&&` *is* of the format prescribed for forwarding references. – Angew is no longer proud of SO Apr 24 '15 at 17:23
  • 1
    @Angew Yes, but as a function parameter, not as a template argument in a template-id. – bogdan Apr 24 '15 at 17:48
  • I have a sneaking suspicion that `identity&&` is rejected by Clang because it triggers an overriding check of the form `is the function parameter an rvalue reference? if so, if it refers to anything other than a plain template parameter, it's not a forwarding reference`. Since `identity` (and `forwarding_reference` from your other question) don't contain the rvalue reference syntax at the 'top level' in the function parameter, they fall through to further processing. This is just a shot in the dark, I haven't looked at the code. – bogdan Apr 26 '15 at 19:38

2 Answers2

8

Consider this code:

template<class T> using identity = T;

template<class T> void foo(identity<T>&&) { } //#1

template<class T> void foo(T&&) { } //#2

int main()
{
  int i{};
  foo(i);
}

Both GCC and Clang reject it because #2 is a redefinition of #1. If they're actually the same template, we could expect #1 to behave in the exact same way as #2, meaning that identity<T>&& should act as a forwarding reference. Following this logic, we don't know which one is right, but GCC is at least consistent.

This is also consistent with a very similar example in the standard at [14.5.7p2].

We should also consider the way template argument deduction can work in this case. If identity were a class template, its form could be matched against the type of the function argument without looking at its definition, allowing the compiler to deduce the template argument for T. However, here we have an alias template; T cannot be deduced to int or int& or anything else unless identity<T> is replaced by T. Otherwise, what are we matching against? Once the replacement is done, the function parameter becomes a forwarding reference.

All of the above supports the idea of identity<T>&& (and identity<T&&>) being treated as equivalent to a forwarding reference.

However, it seems that there's more to this that the immediate replacement of the alias template-id with the corresponding type-id. Paragraph [14.5.7p3] says:

However, if the template-id is dependent, subsequent template argument substitution still applies to the template-id. [ Example:

template<typename...> using void_t = void; 
template<typename T> void_t<typename T::foo> f(); 
f<int>(); // error, int does not have a nested type foo 

—end example ]

This might not seem to have much to do with your example, but it actually indicates that the initial form of the template-id is still taken into account in some cases, independently of the substituted type-id. I guess this opens the possibility that identity<T>&& could actually not be treated as a forwarding reference after all.

This area seems to be underspecified in the standard. This shows in the number of open issues dealing with similar problems, all in the same category in my opinion: in what cases should the initial form of the template-id be taken into account upon instantiation, even though it's supposed to be replaced by the corresponding type-id immediately when encountered. See issues 1980, 2021 and 2025. Even issues 1430 and 1554 could be seen as dealing with similar problems.

In particular, issue 1980 contains the following example:

template<typename T, typename U> using X = T;
template<typename T> X<void, typename T::type> f();
template<typename T> X<void, typename T::other> f();

with the note:

CWG felt that these two declarations should not be equivalent.

(CWG - the Core working group)

A similar line of reasoning could apply to your example, making identity<T>&& not equivalent to a forwarding reference. This could even have practical value, as a straightforward way of avoiding the greediness of a forwarding reference when all you want is an rvalue reference to a deduced T.

So, I think you've raised a very interesting problem. Your example may be worth adding as a note to issue 1980, to make sure this is taken into account when drafting the resolution.

In my opinion, the answer to your question is, for now, a resounding "who knows?".


Update: In the comments to the other, related, question, Piotr S. pointed out issue 1700, which was closed as "not a defect". It refers to the very similar case described in that question, and contains the following rationale:

Because the types of the function parameters are the same, regardless of whether written directly or via an alias template, deduction must be handled the same way in both cases.

I think it applies just as well to the cases discussed here, and settles the issue for now: all these forms should be treated as equivalent to a forwarding reference.

(It will be interesting to see if this is changed indirectly by the resolutions for the other open issues, but they mostly deal with substitution failures rather than deduction by itself, so I guess such an indirect effect is rather unlikely.)


All standard references are to the current working draft, N4431, the second draft after final C++14.

Note that the quote from [14.5.7p3] is a recent addition, included right after the final C++14 version as the resolution of DR1558. I think we can expect further additions in this area as the other issues are resolved in one way or another.

Until then, it may be worth asking this question in the ISO C++ Standard - Discussion group; that should bring it to the attention of the right people.

Community
  • 1
  • 1
bogdan
  • 9,229
  • 2
  • 33
  • 48
  • Your passage from §14.5.7/3 is not present in the C++14 or C++11 standard. – Columbo Apr 26 '15 at 10:12
  • @Columbo Yes, it is the resolution of [DR1558](http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#1558), included in the working draft after the November 2014 meeting, so it can be found in the two drafts after final C++14. I forgot to mention the document version I quoted, I'll update the answer, thanks for the note. – bogdan Apr 26 '15 at 10:32
  • Thank you for this insightful analysis. I have started a discussion on google groups [here](https://groups.google.com/a/isocpp.org/d/topic/std-discussion/s0U6TNU1Qy0/discussion) – Marc Andreson Apr 27 '15 at 15:59
  • @MarcAndreson Nice one; looking forward to seeing their opinions. One niggle: I didn't say *unspecified*, but *under*specified. I shouldn't have used that word, it's confusing, sorry. What I meant by *underspecified* was "not specified clearly enough, needs more details". *Unspecified* has special meaning in the context of the standard ([1.3.26]) and clearly doesn't apply here. – bogdan Apr 27 '15 at 16:17
7

It is not a forwarding reference. C++14 (n4140) 14.8.2.1/3 (emphasis mine):

... If P is an rvalue reference to a cv-unqualified template parameter and the argument is an lvalue, the type “lvalue reference to A” is used in place of A for type deduction.

This is the piece of the standard which specifies how forwarding references work. P, the type of the function parameter, is of type "rvalue reference to identity<T>." identity<T> is the type of the template parameter, but it is not a template parameter itself, so the forwarding reference deduction rule does not apply.

We can also look at what 14.5.7/2 has to say about alias templates:

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.

So the substituted alias is equivalent to the type of T, but 14.8.2.1/3 reads "reference to ... template parameter," not "reference to ... the type of a template parameter."

Angew is no longer proud of SO
  • 167,307
  • 17
  • 350
  • 455
  • 1
    If we assume the implementation is free to substitute the alias template with actual type it points to before the template argument type deduction takes place (or something else, not sure how to call it, I'm referring to what GCC presumably does), this paragraph still applies, doesn't it? - I might be wrong, I just don't know. – Marc Andreson Apr 24 '15 at 15:32
  • @MarcAndreson Any reason why we should be able to assume that? – Angew is no longer proud of SO Apr 24 '15 at 15:44
  • @MarcAndreson Per 14.5.7/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." Again, it is equivalent to the *type,* but the forwarding reference ruling speaks about template parameters, not their types. – Angew is no longer proud of SO Apr 24 '15 at 15:46
  • I was just about to paste the same sentence, but with the "**equivalent**" word emphasised – Marc Andreson Apr 24 '15 at 15:47
  • @MarcAndreson I actually added it to the answer. – Angew is no longer proud of SO Apr 24 '15 at 15:48
  • @Columbo I don't think it's irrelevant. To me, "C++14" means "the published standard." While n4140 is supposed to be identical content-wise, it's not the official thing. – Angew is no longer proud of SO Apr 27 '15 at 08:44
  • @Angew That is an implementation detail that needs to be abstracted away. And you're preventing that abstraction to happen, which is bad. – Columbo Apr 27 '15 at 09:26