Я недавно обнаружил, что декларации друзей чрезвычайно своеобразные правила — если у тебя есть friend
объявление (определение) для функции или класса, который еще не объявлен, он автоматически объявляется (определяется) в непосредственно включающем пространстве имен, но это невидимо для неквалифицированного и квалифицированного поиска; однако, друг функция объявления остаются видимыми через аргумент-зависимый поиск.
struct M {
friend void foo();
friend void bar(M);
};
void baz() {
foo(); // error, unqualified lookup cannot find it
::foo(); // error, qualified lookup cannot find it
bar(M()); // ok, thanks to ADL magic
}
Если вы посмотрите на стандарт (см. связанный ответ), они пошли на значительные длины, чтобы включить это эксцентричное поведение, добавив конкретное исключение в квалифицированном / неквалифицированном поиске со сложными правилами. Конечный результат выглядит для меня крайне запутанным1, с еще одним угловым случаем, чтобы добавить к реализациям. Как либо
friend
объявления для ссылки на существующие имена, период; или жеМне кажется, проще реализовать, уточнить и, что самое важное, понять, мне интересно: почему они заморачивались с этим беспорядком? Какие варианты использования они пытались охватить? Что нарушает какое-либо из этих более простых правил (в частности, второе, которое наиболее похоже на существующее поведение)?
Например, в данном конкретном случае
struct M {
friend class N;
};
N *foo;
typedef int N;
ты получаешь комично шизофренические сообщения об ошибках
<source>:4:1: error: 'N' does not name a type
N *foo;
^
<source>:5:13: error: conflicting declaration 'typedef int N'
typedef int N;
^
<source>:2:17: note: previous declaration as 'class N'
friend class N;
^
где компилятор впервые утверждает, что нет такой вещи, как N
, но немедленно перестает играть глупо, когда вы пытаетесь предоставить противоречивую декларацию.
Что ж, для ответа на этот вопрос вам нужно взглянуть на еще одну важную особенность C ++: шаблоны.
Рассмотрим такой шаблон:
template <class T>
struct magic {
friend bool do_magic(T*) { return true; }
};
Используется в коде, как это:
bool do_magic(void*) { return false; }
int main() {
return do_magic((int*)0);
}
Будет ли код выхода 0
или же 1
?
Ну, это зависит от того, magic
был когда-либо создан с int
где-нибудь наблюдаемый.
По крайней мере, если бы friend
-функции, только объявленные inline, будут найдены обычными правилами поиска.
И вы не можете сломать эту загадку, просто внедрив все возможное, поскольку шаблоны могут быть специализированными.
Это имело место некоторое время, но было объявлено вне закона как «слишком волшебное» и «слишком плохо определенное».
Были дополнительные проблемы с введением имени, поскольку оно было не так четко определено, как ожидалось. Увидеть N0777: альтернатива внедрению имени из шаблонов для большего.
Других решений пока нет …