Difference between a pointer to a standalone and a friend function
Asked Answered
P

3

19

I don't understand why the following does not compile (e.g. in gcc 9.10 or MS VS C++ 2019):

class X {
  public:
    friend bool operator==(int, X const &);
};

int main() {
  2 == X();  // ok...
  static_cast<bool (*)(int, X const &)>(&operator==);  // Error: 'operator==' not defined
  return 0;
}

but the following code is compiled without any issues:

class X {
  public:
};
bool operator==(int, X const &);

int main() {
  2 == X();  // ok...
  static_cast<bool (*)(int, X const &)>(&operator==);  // OK!
  return 0;
}

My expectation is that a friend function (operator==) of X behaves as a standalone function (operator==). What I'm missing? Thanks

Principium answered 3/8, 2020 at 8:44 Comment(0)
S
23

What I'm missing?

An inline friend declaration does not make the function available to ordinary name lookup.

Pay close attention to the error. It does not say that the function is of the wrong type, it simply can't find anything named operator==. This is by design.

Inline friend definitions are only found by argument dependent lookup. Ordinary lookup (such as naming the function to take its address), cannot find it. If you want the function to be available for that purpose, you must provide a namespace scoped declaration.

class X {
  public:
    friend bool operator==(int, X const &) { /* ... */ }
};

bool operator==(int, X const &);
Smoodge answered 3/8, 2020 at 8:50 Comment(3)
Since it's a friend function, should the definition be on free function stead of inside of X?Waggoner
@LouisGo - I don't know if it should. Both are valid. And an inline definition is also lexically in the scope of the class. So it requires less qualifications. Meaning that if the class had a private static member foo, then the inline definition can simply name foo, whereas the out of line definition must use X::foo. Some readability arguments could be made for either form.Smoodge
Thank you, I got it. Was thinking semantically while friend is not own by the declared class, so it should be defined out side of the class. However both cases are valid so it's a topic of code consistency.Waggoner
S
5

From the standard 11.9.3.7:

Such a function is implicitly an inline ([dcl.inline]) function if it is attached to the global module. A friend function defined in a class is in the (lexical) scope of the class in which it is defined. A friend function defined outside the class is not ([basic.lookup.unqual]).

From the standard namespace.memdef/3: (Thanks @StoryTeller)

If a friend declaration in a non-local class first declares a class, function, class template or function template the friend is a member of the innermost enclosing namespace. The friend declaration does not by itself make the name visible to unqualified lookup ([basic.lookup.unqual]) or qualified lookup ([basic.lookup.qual]). [ Note: The name of the friend will be visible in its namespace if a matching declaration is provided at namespace scope (either before or after the class definition granting friendship). — end note ] If a friend function or function template is called, its name may be found by the name lookup that considers functions from namespaces and classes associated with the types of the function arguments ([basic.lookup.argdep]). If the name in a friend declaration is neither qualified nor a template-id and the declaration is a function or an elaborated-type-specifier, the lookup to determine whether the entity has been previously declared shall not consider any scopes outside the innermost enclosing namespace.

The following doesn't work because the function is not visible in the current scope.

  static_cast<bool (*)(int, X const &)>(&operator==);  // Error: 'operator==' not defined
Savory answered 3/8, 2020 at 8:55 Comment(2)
Lexical scope does not have anything to do with this not being found. It's the wrong quote.Smoodge
To clarify, you want to be quoting namespace.memdef/3Smoodge
G
3

The difference is that in the first snippet you only declare the operator in the scope of X, but not outside. There is no operator==(int,X const &) accesible from main. If you fix that and do declare it also outside you only get warnings:

class X {
  public:
    friend bool operator==(int, X const &);
};

bool operator==(int,X const&);    // <--

int main() {
  2 == X();  // ok...
  static_cast<bool (*)(int, X const &)>(&operator==);  // Error: 'operator==' not defined
  return 0;
}

Note however, that for both cases you do need a definition to actually call the operator.

For illustration consider that with

struct foo {
    friend void bar() { 
         std::cout << "this is a inline definition of friend funtion";
    }
};

The only way to access bar from outside foo is to add a declaration outside of foo:

void bar();
Guanabana answered 3/8, 2020 at 8:50 Comment(1)
you wrote: "There is no operator==(int,X const &) accessible from main". I think it is not completely true as you can see in the first line of main(): 2 == X(); which matches == correctly. @StoryTeller - Unslander Monica pointed to "argument dependent lookup". I believe that's the differentiation.Principium

© 2022 - 2024 — McMap. All rights reserved.