Почему я не могу вызвать функцию друга шаблона с явными аргументами шаблона? - PullRequest
2 голосов
/ 07 марта 2020

Рассмотрим следующий пример:

struct S {
    template<typename T = void>
    friend void foo(S) {
    }
};

int main() {
    S s;
    foo(s); // (1)
    foo<void>(s); // (2)
}

My G CC 9.2.0 не может скомпилировать (2) со следующей ошибкой:

a.cpp: In function 'int main()':
a.cpp:10:5: error: 'foo' was not declared in this scope
   10 |     foo<void>(s);
      |     ^~~
a.cpp:10:9: error: expected primary-expression before 'void'
   10 |     foo<void>(s);
      |         ^~~~

Однако (1) работает отлично. Почему это? Как мне вызвать foo с явными аргументами шаблона?

Ответы [ 3 ]

2 голосов
/ 07 марта 2020

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)
}
1 голос
/ 07 марта 2020

Clang в Godbolt смог пролить некоторый свет:

<source>:9:5: warning: use of function template name with no prior declaration in function call with explicit template arguments is a C++20 extension [-Wc++20-extensions]

    foo<void>(s);
    ^

1 warning generated.

Похоже, такой синтаксис не был частью языка до C ++ 20, когда P084R0 прибыл и исправил это.

Поскольку foo() полностью реализован внутри структуры S, в main() не видно шаблона foo. Поэтому компилятор не понимает, что ему нужно сделать ADL, и не может найти foo(). Как сказал @ 0x499602D2.

Одно из решений - обновить компилятор, другое решение - реализовать foo вне S и, необязательно, добавить прямое объявление для предоставления аргументов шаблона по умолчанию:

struct S;

template<typename T = void>
void foo(S); // (a)

struct S {
    template<typename T>
    friend void foo(S); // (b)
};

template<typename T>
void foo(S) { // (c)
    // Needs S to be complete.
}

int main() {
    S s;
    foo(s);
    foo<void>(s);
}

Если вы попытаетесь опустить предварительное объявление (a), вы обнаружите, что не можете добавить аргументы шаблона по умолчанию к (b) по причинам , и не можете добавить его к (c), потому что это переопределение (b) и, следовательно, не может вводить аргументы шаблона по умолчанию.

1 голос
/ 07 марта 2020

Тот факт, что это шаблон, не имеет значения. Функции-друзья, которые определены там, где они объявлены, можно найти только с помощью поиска ADL . Когда вы используете аргументы шаблона, компилятор пытается найти шаблон функции с именем foo с обычным неквалифицированным поиском, и это не удается. foo(s) ищет foo со связанным пространством имен s (глобальное пространство имен) и находит определенную вами функцию друга.

...