Can an identity alias template be a forwarding reference?

余生颓废 提交于 2019-11-30 17:23:01
bogdan

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.

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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!