Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the point of the complicated scoping rules for friend declarations?

I recently discovered that friend declarations scoping follows extremely peculiar rules - if you have a friend declaration (definition) for a function or a class that is not already declared, it is automatically declared (defined) in the immediately enclosing namespace, but it is invisible to non-qualified and qualified lookup; however, friend function declarations remain visible through argument-dependent lookup.

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
}

If you look at the standard (see linked answer), they went to significant lengths to enable this eccentric behavior, adding a specific exception in qualified/non qualified lookup with complex rules. The end result looks to me extremely confusing1, with yet-another-corner case to add to implementations. As either

  • requiring friend declarations to refer to existing names, period; or
  • allowing them to declare stuff as it is now, but without altering the ordinary names lookup (so, such names become visible as if declared "normally" in the enclosing namespace)

seem simpler to implement, to specify and, most importantly, to understand, I wonder: why did they bother with this mess? What use cases were they trying to cover? What breaks under any of those simpler rules (in particular the second one, which is the most similar to the existing behavior)?


  1. For example, in this particular case

    struct M {
       friend class N;
    };
    N *foo;
    typedef int N;
    

    you get comically schizophrenic error messages

    <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;
                     ^
    

    where the compiler first claims that there's no such a thing as N, but immediately stops playing dumb when you try to provide a conflicting declaration.

like image 209
Matteo Italia Avatar asked Aug 31 '18 20:08

Matteo Italia


People also ask

What is the point of friend function?

A friend function is used to access all the non-public members of a class. You can use a friend function to bridge two classes by operating objects of two different classes. It increases the versatility of overloading operators. It enhances encapsulation.

What is the main reason for creating a friend class?

Friend Class A friend class can access private and protected members of other class in which it is declared as friend. It is sometimes useful to allow a particular class to access private members of other class.

What is a friend function Why is it required explain with an example?

In object-oriented programming, a friend function, that is a "friend" of a given class, is a function that is given the same access as methods to private and protected data. A friend function is declared by the class that is granting access, so friend functions are part of the class interface, like methods.

Does friend need forward declaration?

Friend Functions For a free function, it is very straightforward and a forward declaration is not required. We can simply declare the friend as follows: The void Print(const Test& test) function has access to the private members of the Test class. For a member function, it's not as straightforward as the free function.


1 Answers

Well, for answering that, you have to look at another major feature of C++: Templates.

Consider a template such as this:

template <class T>
struct magic {
    friend bool do_magic(T*) { return true; }
};

Used in code like this:

bool do_magic(void*) { return false; }

int main() {
    return do_magic((int*)0);
}

Will the exit-code be 0 or 1?

Well, it depends on whether magic was ever instantiated with int anywhere observable.
At least it would, if friend-functions only declared inline would be found by ordinary lookup-rules.
And you can't break that conundrum by just injecting everything possible, as templates can be specialized.

That was the case for a time, but was outlawed as "too magic", and "too ill-defined".

There were additional problems with name injection, as it wasn't nearly as well-defined as hoped for. See N0777: An Alternative to Name Injection from Templates for more.

like image 86
Deduplicator Avatar answered Oct 12 '22 13:10

Deduplicator