В чем смысл сложных правил определения границ для объявлений друзей?

Я недавно обнаружил, что декларации друзей чрезвычайно своеобразные правила — если у тебя есть 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 объявления для ссылки на существующие имена, период; или же
  • позволяя им объявлять вещи такими, какие они есть сейчас, но не изменяя обычный поиск имен (таким образом, такие имена становятся видимыми, как если бы они были объявлены «нормально» во вложенном пространстве имен)

Мне кажется, проще реализовать, уточнить и, что самое важное, понять, мне интересно: почему они заморачивались с этим беспорядком? Какие варианты использования они пытались охватить? Что нарушает какое-либо из этих более простых правил (в частности, второе, которое наиболее похоже на существующее поведение)?


  1. Например, в данном конкретном случае

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

11

Решение

Что ж, для ответа на этот вопрос вам нужно взглянуть на еще одну важную особенность 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: альтернатива внедрению имени из шаблонов для большего.

13

Другие решения

Других решений пока нет …

По вопросам рекламы [email protected]