Может ли шаблон псевдонима идентичности быть ссылкой пересылки?

Рассмотрим ниже приведенный ниже фрагмент:

template <class T>
using identity = T;

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

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

i является значением lvalue, поэтому если foo объявляет ссылочный параметр пересылки, он должен скомпилироваться. Однако, если identity<T>&& оказывается int&&, он должен вызывать ошибку.

Код компилируется в GCC 6.0.0 (демонстрация).

Код не компилируется в Clang 3.7.0 (демонстрация) с сообщением об ошибке:

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

Какой из них прав?

Ответы

Ответ 1

Рассмотрим этот код:

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);
}

Оба GCC и Clang отклоняют его, потому что #2 является переопределением #1. Если они фактически являются одним и тем же шаблоном, мы могли бы ожидать, что #1 будет вести себя точно так же, как #2, а это означает, что identity<T>&& должен действовать как ссылка для пересылки. Следуя этой логике, мы не знаем, какой из них прав, но GCC по крайней мере непротиворечивый.

Это также согласуется с очень похожим примером в стандарте в [14.5.7p2].

Мы также должны рассмотреть способ работы с аргументом шаблона аргумента. Если identity был шаблоном класса, его форма могла быть сопоставлена ​​с типом аргумента функции, не глядя на его определение, позволяя компилятору вывести аргумент шаблона для T. Однако здесь мы имеем шаблон псевдонима; T не может быть выведено на int или int& или что-либо еще, если identity<T> не заменяется на T. В противном случае, с чем мы согласны? После того, как замена выполнена, параметр функции становится ссылкой пересылки.

Все вышеперечисленное поддерживает идею identity<T>&&identity<T&&>), которая рассматривается как эквивалентная справочной системе пересылки.

Однако, похоже, что к этому добавляется немедленная замена идентификатора шаблона псевдонима соответствующим идентификатором типа. В параграфе [14.5.7p3] говорится:

Однако, если идентификатор шаблона зависит, последующий аргумент шаблона подстановка по-прежнему применяется к идентификатору шаблона. [Пример:

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 пример]

Возможно, это не имеет большого отношения к вашему примеру, но на самом деле это указывает на то, что исходная форма идентификатора шаблона все же учитывается в некоторых случаях независимо от замещенного идентификатора типа. Я предполагаю, что это открывает возможность того, что identity<T>&& фактически не может считаться ссылкой для пересылки.

Эта область, по-видимому, указана в стандарте ниже. Это показывает количество открытых вопросов, связанных с аналогичными проблемами, по-моему, в одной и той же категории: в каких случаях первоначальная форма идентификатора шаблона учитывается при создании экземпляра, хотя предполагается, что он должен быть заменен соответствующий идентификатор типа сразу после его возникновения. См. Вопросы 1980, 2021 и 2025. Даже проблемы 1430 и 1554 могут рассматриваются как имеющие отношение к аналогичным проблемам.

В частности, issue 1980 содержит следующий пример:

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();

с примечанием:

CWG считает, что эти два объявления не должны быть эквивалентными.

(CWG - рабочая группа Core)

Аналогичная линия рассуждений может применяться к вашему примеру, делая identity<T>&& не эквивалентным ссылке пересылки. Это может даже иметь практическое значение, поскольку простой способ избежать жадности ссылки на пересылку, когда все, что вам нужно, - это ссылка rvalue на выведенный T.

Итак, я думаю, вы подняли очень интересную проблему. В качестве примера можно привести примечание к issue 1980, чтобы убедиться, что это учитывается при разработке разрешения.

На мой взгляд, ответ на ваш вопрос на данный момент звучит "кто знает?".


Обновление: в комментариях к другому, связанному, вопросу, Piotr S. указал issue 1700, который был закрыт как "не дефект". Это относится к очень похожему случаю, описанному в этом вопросе, и содержит следующее обоснование:

Поскольку типы параметров функции одинаковы, независимо от того, написаны ли они напрямую или через шаблон псевдонима, в обоих случаях вывод должен выполняться одинаково.

Я думаю, что это применимо также к рассмотренным здесь случаям и решает проблему на данный момент: все эти формы следует рассматривать как эквивалентные справочной информации пересылки.

(Будет интересно посмотреть, косвенно ли это косвенное изменение в разрешениях для других открытых проблем, но в основном они имеют дело с ошибками замены, а не с дедукцией, поэтому я предполагаю, что такой косвенный эффект маловероятен.)


Все стандартные ссылки относятся к текущему рабочему чертежу, N4431, второму проекту после окончательного С++ 14.

Обратите внимание, что цитата из [14.5.7p3] является недавним добавлением, включенным сразу после окончательной версии С++ 14 в качестве разрешения DR1558. Я думаю, мы можем ожидать дальнейших дополнений в этой области, так как другие проблемы решаются так или иначе.

До тех пор, возможно, стоит задать этот вопрос в группе ISO С++ Standard - Discussion; что должно привлечь внимание соответствующих людей.

Ответ 2

Это не ссылка для пересылки. С++ 14 (n4140) 14.8.2.1/3 (акцент мой):

... Если P является ссылкой rval на cv-unqualified параметр шаблона, а аргумент - lvalue, тип "lvalue reference to A" используется в место A для вывода типа.

Это часть стандарта, которая указывает, как работают ссылки перенаправления. P, тип параметра функции, имеет тип "rvalue reference to identity<T>". identity<T> - это тип параметра шаблона, но он не является самим параметром шаблона, поэтому правило ссылочного вывода пересылки не применяется.

Мы также можем посмотреть, что 14.5.7/2 должно сказать о шаблонах псевдонимов:

Когда идентификатор шаблона ссылается на специализацию шаблона псевдонима, он эквивалентен связанному типу полученный путем подстановки его шаблонов-аргументов для параметров шаблона в идентификаторе типа псевдонима шаблон.

Таким образом, замещенный псевдоним эквивалентен типу T, но 14.8.2.1/3 читает "ссылка на... параметр шаблона", а не "ссылка на... тип параметра шаблона."