Рассмотрим следующий фрагмент ниже:
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
Какой из них прав?
Рассмотрим этот код:
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
— конец примера]
Может показаться, что это не имеет большого отношения к вашему примеру, но на самом деле это означает, что в некоторых случаях первоначальная форма идентификатора шаблона все еще учитывается, независимо от подставленного идентификатора типа. Я думаю, что это открывает возможность того, что identity<T>&&
на самом деле не может рассматриваться как ссылка для пересылки.
Эта область, по-видимому, недостаточно указана в стандарте. Это показывает количество открытых вопросов, связанных со схожими проблемами, и все они, по моему мнению, относятся к одной и той же категории: в каких случаях следует учитывать первоначальную форму идентификатора шаблона при создании экземпляра, даже если он должен быть заменен соответствующий идентификатор типа сразу же при обнаружении. Смотрите вопросы 1980, 2021 а также 2025. Четные вопросы 1430 а также 1554 можно рассматривать как имеющие дело с аналогичными проблемами.
Особенно, выпуск 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 — Основная рабочая группа)
Аналогичная аргументация может относиться к вашему примеру, делая identity<T>&&
не эквивалентен отправочной ссылке. Это может даже иметь практическую ценность, поскольку является простым способом избежать жадности ссылки на пересылку, когда все, что вам нужно, это ссылка на значение для выведенного T.
Итак, я думаю, что вы подняли очень интересную проблему. Ваш пример, возможно, стоит добавить в качестве примечания к выпуск 1980, чтобы убедиться, что это учитывается при составлении резолюции.
На мой взгляд, ответом на ваш вопрос на данный момент является громкое «кто знает?».
Обновление: в комментариях к другим, связанным, вопрос, Петр С. указал выпуск 1700, который был закрыт как «не дефект». Это относится к очень похожему случаю, описанному в этом вопросе, и содержит следующее обоснование:
Поскольку типы параметров функции одинаковы, независимо от того, записаны они напрямую или через шаблон псевдонимов, вычет должен обрабатываться одинаково в обоих случаях.
Я думаю, что это применимо также и к случаям, обсуждаемым здесь, и решает вопрос на данный момент: все эти формы должны рассматриваться как эквивалентная ссылка для пересылки.
(Будет интересно посмотреть, будет ли это косвенно изменено в решениях по другим открытым вопросам, но они в основном касаются сбоев замещения, а не самих вычетов, поэтому я полагаю, что такой косвенный эффект довольно маловероятен.)
Все стандартные ссылки относятся к текущему рабочему проекту, N4431, второму проекту после окончательного C ++ 14.
Обратите внимание, что цитата из [14.5.7p3] является недавним дополнением, включенным сразу после финальной версии C ++ 14 в качестве разрешения DR1558. Я думаю, что мы можем ожидать дальнейших дополнений в этой области, так как другие вопросы решаются так или иначе.
До тех пор, возможно, стоит задать этот вопрос в Стандарт ISO C ++ — Обсуждение группа; это должно довести это до сведения правильных людей.
Это не ссылка для пересылки. C ++ 14 (n4140) 14.8.2.1/3 (выделено мной):
… Если
P
является Rvalue ссылка на резюме без квалификации
параметр шаблона и аргумент является lvalue, тип «lvalue ссылка наA
”Используется в
местоA
для вывода типа.
Это часть стандарта, которая определяет, как работает переадресация ссылок. P
тип параметра функции имеет тип «rvalue ссылка на identity<T>
«. identity<T>
это тип параметра шаблона, но он не является самим параметром шаблона, поэтому правило вывода ссылки на пересылку не применяется.
Мы также можем посмотреть, что 14.5.7 / 2 говорит о шаблонах псевдонимов:
Когда Шаблон-идентификатор относится к специализации шаблона псевдонима, это эквивалентно тип
полученный путем замены его Шаблон-аргументы для Шаблон-параметры в тип-идентификатор псевдонима
шаблон.
Таким образом, замещенный псевдоним эквивалентен тип из T
, но 14.8.2.1/3 гласит «ссылка на … параметр шаблона», а не «ссылка на … тип параметра шаблона».