public friend swap member function
Asked Answered
B

2

216

In the beautiful answer to the copy-and-swap-idiom there is a piece of code I need a bit of help:

class dumb_array
{
public:
    // ...
    friend void swap(dumb_array& first, dumb_array& second) // nothrow
    {
        using std::swap; 
        swap(first.mSize, second.mSize); 
        swap(first.mArray, second.mArray);
    }
    // ...
};

and he adds a note

There are other claims that we should specialize std::swap for our type, provide an in-class swap along-side a free-function swap, etc. But this is all unnecessary: any proper use of swap will be through an unqualified call, and our function will be found through ADL. One function will do.

With friend I am a bit on "unfriendly" terms, I must admit. So, my main questions are:

  • looks like a free function, but its inside the class body?
  • why isn't this swap static? It obviously doesn't use any member variables.
  • "Any proper use of swap will find out swap via ADL"? ADL will search the namespaces, right? But does it also look inside classes? Or is here where friend comes in?

Side-questions:

  • With C++11, should I mark my swaps with noexcept?
  • With C++11 and its range-for, should I place friend iter begin() and friend iter end() the same way inside the class? I think the friend is not needed here, right?
Berck answered 17/4, 2011 at 18:35 Comment(2)
Considering the side question about range-based for: it's better idea to write member functions and leave the range access on begin() and end() in std namespace (§24.6.5), range-based for internally uses these from global or std namespace (see §6.5.4). However, it comes with downside that these functions are part of <iterator> header, if you do not include it, you might want to write them yourself.Sachi
why isn't it static - because a friend function isn't a member function at all.Dacia
V
217

There are several ways to write swap, some better than others. Over time, though, it was found a single definition works best. Let's consider how we might think about writing a swap function.


We first see that containers like std::vector<> have a single-argument member function swap, such as:

struct vector
{
    void swap(vector&) { /* swap members */ }
};

Naturally, then, our class should too, right? Well, not really. The standard library has all sorts of unnecessary things, and a member swap is one of them. Why? Let's go on.


What we should do is identify what's canonical, and what our class needs to do to work with it. And the canonical method of swapping is with std::swap. This is why member functions aren't useful: they aren't how we should swap things, in general, and have no bearing on the behavior of std::swap.

Well then, to make std::swap work we should provide (and std::vector<> should have provided) a specialization of std::swap, right?

namespace std
{
    template <> // important! specialization in std is OK, overloading is UB
    void swap(myclass&, myclass&)
    {
        // swap
    }
}

Well that would certainly work in this case, but it has a glaring problem: function specializations cannot be partial. That is, we cannot specialize template classes with this, only particular instantiations:

namespace std
{
    template <typename T>
    void swap<T>(myclass<T>&, myclass<T>&) // error! no partial specialization
    {
        // swap
    }
}

This method works some of the time, but not all of the time. There must be a better way.


There is! We can use a friend function, and find it through ADL:

namespace xyz
{
    struct myclass
    {
        friend void swap(myclass&, myclass&);
    };
}

When we want to swap something, we associate std::swap and then make an unqualified call:

using std::swap; // allow use of std::swap...
swap(x, y); // ...but select overloads, first

// that is, if swap(x, y) finds a better match, via ADL, it
// will use that instead; otherwise it falls back to std::swap

What is a friend function? There is confusion around this area.

Before C++ was standardized, friend functions did something called "friend name injection", where the code behaved as if if the function had been written in the surrounding namespace. For example, these were equivalent pre-standard:

struct foo
{
    friend void bar()
    {
        // baz
    }
};

// turned into, pre-standard:    

struct foo
{
    friend void bar();
};

void bar()
{
    // baz
}

However, when ADL was invented this was removed. The friend function could then only be found via ADL; if you wanted it as a free function, it needed to be declared as so (see this, for example). But lo! There was a problem.

If you just use std::swap(x, y), your overload will never be found, because you've explicitly said "look in std, and nowhere else"! This is why some people suggested writing two functions: one as a function to be found via ADL, and the other to handle explicit std:: qualifications.

But like we saw, this can't work in all cases, and we end up with an ugly mess. Instead, idiomatic swapping went the other route: instead of making it the classes' job to provide std::swap, it's the swappers' job to make sure they don't use qualified swap, like above. And this tends to work pretty well, as long as people know about it. But therein lies the problem: it's unintuitive to need to use an unqualified call!

To make this easier, some libraries like Boost provided the function boost::swap, which just does an unqualified call to swap, with std::swap as an associated namespace. This helps make things succinct again, but it's still a bummer.

Note that there is no change in C++11 to the behavior of std::swap, which I and others mistakenly thought would be the case. If you were bit by this, read here.


In short: the member function is just noise, the specialization is ugly and incomplete, but the friend function is complete and works. And when you swap, either use boost::swap or an unqualified swap with std::swap associated.


†Informally, a name is associated if it will be considered during a function call. For the details, read §3.4.2. In this case, std::swap normally isn't considered; but we can associate it (add it to the set of overloads considered by unqualified swap), allowing it to be found.

Vaunt answered 17/4, 2011 at 19:24 Comment(27)
Top answer again. :) Maybe add exactly what you mean with with std::swap` associated`?Matronage
Just one simple request: can you provide reference to the part of C++11 draft stating that std::swap must use ADL-found swap first? I cannot seem to find it (only relevant part is §17.6.3.2, but that considers only standard library).Sachi
@GMan: Yes, I know about this. But considering that std::swap in <utility> (§20.2.2) doesn't mention Swappable concept, it doesn't seem that any general use of std::swap is guaranteed to use fitting ADL-found swap.Sachi
I disagree that the member function is just noise. A member function allows for e.g. std::vector<std::string>().swap(someVecWithData);, which isn't possible with a swap free function because both arguments are passed by non-const reference.Rudd
@ildjarn: You can do it on two lines. Having the member function violates the DRY principle.Vaunt
@GMan : The DRY principle doesn't apply if one is implemented in term of the other. Otherwise no one would advocate a class with implementations of operator=, operator+, and operator+=, but clearly those operators on relevant classes are accepted/expected to exist for symmetry. Same goes for member swap + namespace-scoped swap in my opinion.Rudd
@GMan : And, it would be 3 or 4 lines vs. 1 to get the same scoping counting braces, not 2 vs. 1. ;-]Rudd
@ildjarn: operator+ and operator+= are operators in the language; that is, if + works I expect += to, as well. Contrarily, std::swap has no partners. And line count doesn't matter, how often do you do it anyway? Wrap it up in a function, if it bothers you.Vaunt
I once was a big fanboi of ADL. But slowly, I'm moving towards hating it.Factional
@Johannes: Why's that? All the nuance about it? D got rid of it by dropping namespaces.Vaunt
@GMan I think it's considering too much functions. Little known, but even a function<void(A*)> f; if(!f) { } can fail just because A declares an operator! that accepts f equally well as f's own operator! (unlikely, but can happen). If function<>'s author thought "ohh I have an 'operator bool', why should I implement 'operator!'? That would violate DRY!", that would be fatal. You just need to have an operator! implemented for A, and A having a constructor for a function<...>, and things will break, because both candidates will require user defined conversions.Factional
@Johannes: Wow, that's pretty crappy. :/Vaunt
It's IMO a serious deal breaker for generic functions that want to operate even on parameter packs whose types can contain arbitrary classes that each have their own operators. Problems also show up because ADL will require that A be instantiated if it's a class template specialization, which results in unexpected failures sometimes. See llvm.org/bugs/show_bug.cgi?id=9440 and the following comments / linked GCC PR.Factional
@GMan : The bottom line is that namespace-scoped swap and member swap can offer different semantics, so by definition it's not a violation of DRY to have both, especially given that one is implemented in terms of the other. If the semantics were always identical then I'd agree with you.Rudd
@ildjarn: I'll retract my DRY comment, then, but it's still noise. It's a function that's rarely used, that mimics an existing function. If swapping with a temporary is your only example, that's not an argument at all, just skip the temporary on it's own line or write a utility function.Vaunt
@Vaunt Since C++0x now became C++11, is "Luckily, C++0x fixes this. It was mandated that std::swap, internally, selects overloads (including those found via ADL) first!" still true? I had a discussion with someone on cpp11.generisch.de/swap-operator where we were not sure if std::swap(a,b); now can be used safely/idiomatically.Berck
@towi: Unfortunately, no. It maintains its old behavior, the change was a tidbit of hope and misinformation that spread across StackOverflow (where it started, I don't know). I asked after C++11 came to be as well: https://mcmap.net/q/15705/-does-c-11-change-the-behavior-of-explicitly-calling-std-swap-to-ensure-adl-located-swap-39-s-are-found-like-boost-swap/87234. Thanks for bringing this instance to my attention.Vaunt
@JohannesSchaub-litb but one wouldn't find himself in such a situation because of taking care of always applying the safe bool idiom no ?Nimesh
The GotW you mention in your answer (gotw.ca/gotw/084.htm) lists member swap as needed so you should not cite this article as a proof of your opposite idea.Frap
@Vaunt what does UB mean? "important! specialization in std is OK, overloading is UB"Bender
@athos: Undefined behavior.Vaunt
Let's consider how we might think about writing a [member] swap function. Naturally, then, our class should too, right? Well, not really. The standard library has all sorts of unnecessary things, and a member swap is one of them. The linked GotW advocates for member swap function.Keely
Finally standardized in C++20, see this.Directed
Since C++20, specializing standard function templates is UB too. And std::swap won't even be an actual function template.Appellant
@Rudd swap should work for non-const glvalue only. std::vector<T>::swap is provided before C++11 so I think that's the reason it hasn't been marked as a lvalue-only member function.Breger
@Rudd And on the day of 2022, I think the example std::vector<std::string>().swap(someVecWithData); is weird now. It should be done by move-semantic since C++11...Breger
But like we saw, this can't work in all cases - why, I don't understand this comment.Radioisotope
P
8

That code is equivalent (in almost every way) to:

class dumb_array
{
public:
    // ...
    friend void swap(dumb_array& first, dumb_array& second);
    // ...
};

inline void swap(dumb_array& first, dumb_array& second) // nothrow
{
    using std::swap; 
    swap(first.mSize, second.mSize); 
    swap(first.mArray, second.mArray);
}

A friend function defined inside a class is:

  • placed in the enclosing namespace
  • automatically inline
  • able to refer to static members of the class without further qualification

The exact rules are in section [class.friend] (I quote paragraphs 6 and 7 of the C++0x draft):

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.

Such a function is implicitly inline. 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.

Phenylalanine answered 17/4, 2011 at 18:41 Comment(14)
Actually, friend functions are not placed in the enclosing namespace, in standard C++. The old behavior was called "friend name injection", but was superseded by ADL, replaced in the first standard. See the top of this. (The behavior is quite similar, though.)Vaunt
@GMan: Did this change between C++03 and C++0x?Phenylalanine
the inline is clear. Ok, there is another answer helping me out here.Berck
Not really equivalent. The code in the question makes it so that swap is only visible to ADL. It's a member of the enclosing namespace, but its name is not visible to the other name lookup forms. EDIT: I see that @GMan was faster again :) @Ben it's always been that way in the ISO C++ :)Factional
@Ben: No, friend injection never existed in a standard, but it was widely used before which is why the idea (and compiler support) tended to carry on, but it's technically not there. friend functions are only found by ADL, and if they need to just be free functions with friend access, they need to both be declared as friend within the class, and as a normal free function declaration outside the class. You can see that necessity in this answer, for example.Vaunt
I think I am getting there. but... @JohannesSchaub: "other name lookup forms? What is not possible this way? Do you have an example?Berck
@towi: Because the friend function is at namespace scope, the answers to all three of your questions should become clear: (1) It is a free function, plus has friend access to private and protected members of the class. (2) It's not a member at all, neither instance nor static. (3) ADL doesn't search within classes but this is ok because the friend function has namespace scope.Phenylalanine
@GMan, @Johannes: The C++0x draft says that the friend function has namespace scope. Does that represent a change from C++03, or is it still not a namespace member (and therefore cannot be qualified by the namespace)?Phenylalanine
@Ben. In the spec, the function is a namespace member, and the phrase "the function has namespace scope" can be interpreted to say that the function is a namespace member (it pretty much depends on the context of such a statement). And it adds a name to that namespace that is only visible to ADL (actually, IIRC some part contradict other parts in the spec on whether or not any name is added. But the addition of a name is needed to detect incompatible declarations added to that namespace, so in fact, an invisible name is added. See the note at 3.3.1p4).Factional
@Johannes: Not how I understand that. [basic.scope.hiding] 3.3.10p5 says "If a name is in scope and is not hidden it is said to be visible." [class.friend] provides that the function has namespace scope. None of 3.3.10p1-4 indicate that the function is hidden, therefore it is visible.Phenylalanine
@Ben it doesn't say that the function's name has namespace scope. It says that the function itself shall have namespace scope. The term "scope" and related terms are overloaded and are even inconsistently defined. See open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#554 . So whenever the term "scope" is used, the real meaning of it actually depends on the context of its use, and not merely on what 3.3 says about its definition. In this context, other paragraphs say that the name of a friend is not found by "ordinary name lookup", unless explicitly declared in the namespace.Factional
My belief is that the phrase "can be defined if ... the function has namespace scope." just forbids struct A { void f(); struct B { friend void f() { } }; };. Several implementations accept this, but the wording of the standard says that "f" shall be looked up, and scopes outside the innermost enclosing namespace shall be ignored. the scope of "A" is inside the innermost enclosing namespace, so its scope should not be ignored. "f" is declared there, so friend void f() { } will try to define a non-namespace-member, which I think the paragraph intends to forbid.Factional
But perhaps the phrase "can be defined if ... the function has namespace scope." was just added for redundancy, to be especially clear that such things can only be used to define namespace members, without any more other intentions. But I don't think it says that the function's name is visible in the enclosing namespace. The statement starts with "can be defined if...".Factional
@Johannes: I agree that the standard forbids that. But doesn't it seem that a friend declaration which both is a definition and also introduces the name must put that name into the namespace, or else that rule would apply and forbid it too?Phenylalanine

© 2022 - 2024 — McMap. All rights reserved.