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

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

1 ответ

Решение

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

Другие вопросы по тегам