В чем смысл сложных правил определения границ для объявлений друзей? - PullRequest
0 голосов
/ 31 августа 2018

Я недавно обнаружил, что область действия объявлений друзей соответствует чрезвычайно своеобразным правилам - если у вас есть объявление (определение) friend для функции или класса, который еще не объявлен, он автоматически объявляется (определяется) ) в непосредственно включенном пространстве имен , но оно невидимо для неквалифицированного и квалифицированного поиска; однако объявления друзей function остаются видимыми в зависимости от аргументов.

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 Ответ

0 голосов
/ 01 сентября 2018

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

...