friend
определения функций в теле класса не делают функцию friend
видимой в пределах области видимости, в которой они находятся, для обычного поиска без определения имени (хотя они помещаются в эту область имен).
В порядке чтобы сделать его видимым, вам нужно добавить объявление для шаблона в области имен (не имеет значения, происходит ли это до или после определения):
struct S {
template<typename T = void>
friend void foo(S) {
}
};
template<typename T>
void foo(S);
int main() {
S s;
foo(s); // (1)
foo<void>(s); // (2)
}
Теперь возникает вопрос, почему foo(s)
работает. Это из-за аргумент-зависимого поиска. В вызове функции без вложенного спецификатора имени классы и окружающие пространства имен типов аргумента вызова (и других) также ищутся для соответствующих перегрузок функции. Для зависимого от аргумента поиска friend
s, объявленные только в теле класса, являются видимыми. Таким образом, найдена подходящая функция для вызова foo(s)
.
foo<void>(s)
должна работать так же, потому что имя неквалифицированное и s
имеет тип S
, поэтому ADL должен снова найти друга foo
внутри S
.
Однако есть еще одна проблема, которую следует рассмотреть. Когда компилятор читает foo
, он должен решить, может ли foo
быть шаблоном или нет, потому что он изменяет синтаксический анализ <
после foo
.
. Чтобы решить это, выполняется поиск по неквалифицированному имени. на foo
. До C ++ 20 foo
считалось бы именем шаблона, только если этот поиск находит какой-либо шаблон с таким именем. Но поиск по неквалифицированному имени ничего не находит в вашем случае, потому что единственный foo
не виден обычному поиску по неквалифицированному имени. Следовательно, foo
не будет считаться шаблоном, а foo<void>
не будет анализироваться как идентификатор шаблона.
В C ++ 20 правило было изменено, и если при поиске по имени без квалификаций будет найдена обычная функция с таким именем или вообще ничего , тогда foo<void>
также будет считаться идентификатором шаблона. В этом случае следующий ADL для вызова найдет foo
, и вызов будет успешным.
Таким образом, код будет работать как в C ++ 20 и до C ++ 20, вам на самом деле нужно всего лишь объявите любой шаблон по имени foo
, чтобы получить foo<void>(s)
, чтобы найти подружку foo
по ADL. Например:
struct S {
template <typename T = void>
friend void foo(S) {}
};
template<int>
void foo();
int main() {
S s;
foo(s); // (1)
foo<void>(s); // (2)
}