Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can an identity alias template be a forwarding reference?

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?

like image 651
Marc Andreson Avatar asked Apr 24 '15 15:04

Marc Andreson


People also ask

What is an alias template?

Alias templates are a way to give a name to a family of types. Template parameters can be types, non-types, and templates themselves.

What is alias c++?

You can use an alias declaration to declare a name to use as a synonym for a previously declared type. (This mechanism is also referred to informally as a type alias). You can also use this mechanism to create an alias template, which can be useful for custom allocators.


1 Answers

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.

like image 52
bogdan Avatar answered Oct 16 '22 20:10

bogdan