Ответ 1
Я считаю, что это ошибка. Я хочу приблизиться к нему с этого направления. Какие морщины добавляют тип заполнителя auto
, по сравнению с указанным типом возврата? Из [dcl.spec.auto]:
Тип заполнителя может появляться с объявлением функции в описании-specifier-seq, type-specifier-seq, function-function-id или trailing-return-type в любом контексте, где такой декларатор действителен. Если декларатор функции включает в себя тип trailing-return-type (8.3.5), то trailing-return-type указывает объявленный тип возврата функции. В противном случае декларатор функции должен объявить функцию. Если объявленный тип возврата функция содержит тип заполнителя, возвращаемый тип функции выводится из операторов return в теле функции, если таковая имеется.
auto
может отображаться в foo
декларации и определении и действителен.
Если тип объекта с неопределенным типом заполнителя необходим для определения типа выражения, программа плохо сформирована. Однако, как только оператор
return
был замечен в функции, возвращаемый тип выведенный из этого утверждения, может использоваться в остальной части функции, в том числе в других операторах return. [Пример:auto n = n; // error, n’s type is unknown auto f(); void g() { &f; } // error, f’s return type is unknown auto sum(int i) { if (i == 1) return i; // sum’s return type is int else return sum(i-1)+i; // OK, sum’s return type has been deduced }
-end пример]
В первый раз, когда нам нужно определить тип выражения, возвращаемый тип функции уже будет выведен из return
в определении foo()
, поэтому это все еще актуально.
Редекларации или специализации шаблона функции или функции с объявленным типом возврата, который использует тип заполнителя также должен использовать этот заполнитель, а не выведенный тип.
Мы используем auto
в обоих местах, поэтому мы не нарушаем это правило.
Короче говоря, существует несколько вещей, которые различают конкретный тип возвращаемого значения из возвращаемого типа заполнителя из объявления функции. Но все примеры использования auto
в этом примере верны, поэтому область пространства имен foo
должна рассматриваться как переопределение и определение первого объявленного friend auto foo
внутри шаблона класса bar
. Тот факт, что кланг принимает первое слово как receclaration для типа возврата int
, но не для auto
, и для auto
нет никакого релевантного различия, определенно предполагает, что это ошибка.
Кроме того, если вы отбросите параметр шаблона int I
, чтобы вы могли называть foo
неквалифицированным, clang сообщит о вызове как неоднозначное:
std::cout << foo(fizz::bar<int, float>{});
main.cpp:26:18: error: call to 'foo' is ambiguous
std::cout << foo(fizz::bar<int, float>{});
^~~
main.cpp:10:21: note: candidate function [with Us = <int, float>]
friend auto foo(const bar<Us...> &);
^
main.cpp:17:10: note: candidate function [with Ts = <int, float>]
auto foo(const bar<Ts...>& b)
^
Итак, у нас есть два шаблона функций с именем foo
в том же пространстве имен (поскольку из [namespace.memdef] объявление friend
для foo
поместит его в ближайшее окружение), которое принимает те же аргументы и имеет тот же тип возврата (auto
)? Это не должно быть возможным.