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

Рассмотрим следующий фрагмент ниже:

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

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

2 ответа

Решение

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

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 года, чтобы убедиться, что это учтено при составлении резолюции.

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


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

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

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

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


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

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

До тех пор, возможно, стоит задать этот вопрос в группе по обсуждению стандарта ISO C++; это должно привлечь внимание нужных людей.

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

... Если P является rvalue ссылкой на неквалифицированный cv параметр шаблона, а аргумент является lvalue, тип "lvalue ссылка на A”Используется вместо A для вывода типа.

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

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

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

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

Другие вопросы по тегам