C++ friend function hidden by class function?
Asked Answered
H

6

11

Minimal example:

class A
{
    friend void swap(A& first, A& second) {}
    void swap(A& other) {}
    void call_swap(A& other)
    {
        swap(*this, other);
    }
};

int main() { return 0; }

g++ 4.7 says:

friend.cpp: In member function ‘void A::call_swap(A&)’:
friend.cpp:7:20: error: no matching function for call to ‘A::swap(A&, A&)’
friend.cpp:7:20: note: candidate is:
friend.cpp:4:7: note: void A::swap(A&)
friend.cpp:4:7: note:   candidate expects 1 argument, 2 provided

Outcomment line 4:

// void swap(A& other) {}

...and it works fine. Why, and how to fix this, if I want to keep both variants of my swap function?

Hon answered 26/8, 2013 at 17:42 Comment(5)
This looks like a compiler bug to me?Aphonia
@McKay: might be. However, clang gives me the same error (with different explenation).Hon
How about defining the function outside the class in the global namespace?Forborne
@UchiaItachi: wow, does not work either! Seems like friend is not the problem here?Hon
You ran afoul of C++ trying to be clever and not impose that member methods (and variables) only be accessible after this->. It's a convenient syntax trick, certainly, but as a result in order to avoid unknowingly calling non-member functions the name lookup rules specify that the class members hide the surrounding names. While it is reasonable for variables (similar to nested scopes), for functions it breaks expectations of proper overload resolution :/Synaeresis
C
8

I believe it is because the compiler is trying to find the function within the class. This should be a minimalistic change to make it work (it works in Visual Studio 2012):

class A; // this and the next line are not needed in VS2012, but
void swap(A& first, A& second); // will make the code compile in g++ and clang++

class A
{
    friend void swap(A& first, A& second) {}
    void swap(A& other) {}
    void call_swap(A& other)
    {
        ::swap(*this, other); // note the scope operator
    }
};

int main() { return 0; }
Cynara answered 26/8, 2013 at 19:15 Comment(4)
No, this shouldn't work. Clang++ doesn't accept it and the Standard says: "A name prefixed by the unary scope operator :: is looked up in global scope, in the translation unit where it is used. The name shall be declared in global namespace scope or shall be a name whose declaration is visible in global scope because of a using-directive" in [basic.lookup.qual]/4. Although the name of the friend function is in global scope, it hasn't been declared there.Aerobe
It does compile in Visual Studio 2012. I put in a print statement within the friend definition and ran it to confirm that the friend function does get executed and it did.Cynara
I don't doubt that it works in VS2012. I doubt that it should work, meaning that it's either a bug in the VC++ compiler or at least a difference in interpreting the Standard. The OP uses g++ which doesn't accept it either.Aerobe
Thanks, understood. Having just a declaration outside makes the code compile and work on g++ and clang++: coliru.stacked-crooked.com/…Cynara
M
7

Why

Inside the class, names scoped within the class hide those in the surrounding namespace; so the friend (whose name is scoped in the namespace, but not directly accessible there) is hidden by the member (scoped in the class) and not available as a potential overload here. (Update: or perhaps it's a bit more complicated than that, as mentioned in the comments. The scope and name lookup rules are a bit hard to follow, especially when friends are involved).

how to fix this, if I want to keep both variants of my swap function?

There's no perfect solution. If the functions both do the same thing, then just use the member from other member functions. If you declare the friend outside the class definition, then it's accessible as ::swap; but this is a bit fragile if you put the class in a different namespace. (Update: or use a static member function as suggested by this answer; I didn't think of that).

Mcdonnell answered 26/8, 2013 at 17:57 Comment(7)
I don't think the scope argument applies here: [class.friend]/7 "A friend function defined in a class is in the (lexical) scope of the class in which it is defined.". However, a class member found using unqualified lookup hides any name found using ADL as per [basic.lookup.argdep]/3Aerobe
@DyP: Yes, you're probably right. I won't try to make my answer completely accurate, since my brain will probably melt if I try.Mcdonnell
@DyP I'm almost certain all that means is that you are in class scope within the friend function definition, not at all that the friend function is added to the list of candidates for overload resolution. The friend function exists at an outer scope, and the member is the only function in the scope of another class member.Floccus
@MarkB You might be right, [class.friend]/6 says "A function can be defined in a friend declaration of a class if and only if the class is a non-local class (9.8), the function name is unqualified, and the function has namespace scope." Which probably doesn't mean iff it has namespace scope, it can be defined inside the class. But then, it contradicts /7.Aerobe
Ah, thanks! It works using '::swap' and declaring the function above the class.Hon
@DyP: The closest I got to a definition of lexical scope in the standard is the extended quote in 9.7/4 Like a member function, a friend function (11.3) defined within a nested class is in the lexical scope of that class; it obeys the same rules for name binding as a static member function of that class (9.4), but it has no special access rights to members of an enclosing class.Dragonet
@DavidRodríguez-dribeas I guess it would've been clearer if it was "and the name of this function has namespace scope" in [class.friend]/7.Aerobe
M
7

As a workaround, you can declare a static version of swap. Then, you can declare the friend version to call the static version.

class A
{
public:
    friend void swap(A& first, A& second) { A::swap(first, second); }
private:
    static void swap(A& first, A& second) {}
    void swap(A& other) {}
    void call_swap(A& other)
    {
        swap(*this, other);
    }
};

int main () {
    A a, b;
    swap(a, b);
}
Moralez answered 26/8, 2013 at 17:59 Comment(0)
T
3

Keep to the standard swap idiom, and you won't have a problem:

void call_swap(A& other) {
  using std::swap;
  swap(*this, other);
}

Or use the Boost.Swap wrapper:

void call_swap(A& other) {
  boost::swap(*this, other);
}

This is pretty much equivalent to @Juan's solution, except you're not writing the helper yourself.

Tanny answered 26/8, 2013 at 18:17 Comment(14)
But that won't find the friend function either.Mcdonnell
Yes, it will. It will find it with ADL.Tanny
@SebastianRedl Agreed, though for more difficult classes, this will not work.Hon
@MikeSeymour The code is not like in the question. The code in question finds a member function, and thus suppresses ADL. This code finds a free function, and thus allows ADL, which will find the friend. That's why it works.Tanny
@DyP Nope, no removing of any member function needed. ADL (once it happens) doesn't care at all about member functions.Tanny
@Hon It will work for absolutely all cases. The standard swap idiom is completely reliable, as long as you put your swap overloads in the right place, i.e. in the namespace of the class (declaring it as a friend inside works for that).Tanny
Hmm it might work, but because of unqualified name lookup, not ADL. ADL is suppressed by [basic.lookup.argdep]/3.Aerobe
@DyP What part of that? X is {using declaration for std::swap}, which matches none of the three suppression bullets, so Y is the ADL result, which is {friend swap(A&, A&)}.Tanny
@MikeSeymour No, it will do overload resolution between std::swap (using declaration found via unqualified lookup) and swap(A&, A&) (found via ADL), and the latter will win because it's not a template.Tanny
@SebastianRedl: Yes, I guess you're right. Sorry for the confusion; my brain tends to melt when I think too hard about name lookup.Mcdonnell
Isn't the member function found also via unqualified lookup? (i.e. isn't this member function in X?)Aerobe
@DyP No, unqualified lookup stops the moment it finds results, so it stops at the using declaration and doesn't move on to class scope.Tanny
channel9.msdn.com/Series/C9-Lectures-Stephan-T-Lavavej-Core-C-/… explains this stuff pretty well.Tanny
@SebastianRedl: You are indeed right, it works in a more complicated example. Thanks! Though not that practicable to always write the using line every time. Is there a shortcut to that? (which does not use macros?)Hon
H
0

You can also use a helper function, as in this case

template<class T>
void swap_elems(T& lhs, T& rhs)
{
    using namespace std;
    swap(lhs, rhs);
}

class A
{
friend void swap(A& first, A& second) { first.swap(second); }

  public:
    void swap(A& other) {}
    void call_swap(A& other)
    {
        swap_elems(*this, other);
    }
};
Haitian answered 26/8, 2013 at 18:13 Comment(0)
F
0

What you're observing here is that in absence of a previous declaration of a friend function, friendship within a class injects the name into the enclosing namespace, but NOT into the class scope. The only thing that happens at class scope is that the function named is granted access to private attributes.

This leaves only one swap function in the class scope (the member with one parameter) so that's the only candidate overload. Once you've found a candidate even if overload resolution fails you will never try another enclosing scope (shadowing).

If you really need both versions (and step back to make sure you do), put the implementation into a function like swap_impl which you call from the friend and the member.

Floccus answered 26/8, 2013 at 18:17 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.