views:

289

answers:

3

So, I have a macro.

// swap_specialize.hpp
#include <algorithm>

#ifndef STD_SWAP_SPECIALIZE
#define STD_SWAP_SPECIALIZE( CLASSNAME )      \
    namespace std {         \
    template<> inline                 \
    void swap( CLASSNAME & lhs, CLASSNAME & rhs ) \
    { lhs.swap(rhs); } }
#endif

So then I have a class

// c.hpp
#include <vector>
#include "swap_specialize.hpp"
class C
{
    public:
        C();

        void swap(C& rhs)
        {
            data_.swap(rhs.data_);
        }
        C& operator=(C rhs)
        {
            rhs.swap(*this);
            return *this;
        }
    private:
        std::vector<int> data_;
}

STD_SWAP_SPECIALIZE(C)

Is it bad, stylistically, to do this? Is it a code smell? Or is it an OK practice?

A: 

I don't see it buying you much. For non-template classes, it saves you very few lines. And for template classes, when you want to specialize generically (i.e. for all T), it just won't work.

Pavel Minaev
A: 

Assuming you are doing to use STD_SWAP_SPECIALIZE() for a number of other classes, I this this is perfectly reasonable. After all it's much more readable to have a series of

STD_SWAP_SPECIALIZE(Foo)
STD_SWAP_SPECIALIZE(Bar)
STD_SWAP_SPECIALIZE(Baz)

Than the equivilent expanded code. Also, if the expansion of STD_SWAP_SPECIALIZE was a little larger, then the macro definition gives you the code in a single place if it needed changing. (As it is the template definition is pretty small in your example then it's probably a moot point).

Dave Rigby
+6  A: 

I would say it's OK if it increases readability. Judge yourself. Just my two cents: Specializing std::swap isn't really the right way to do this. Consider this situation:

my_stuff::C c, b;
// ...
swap(c, b);
// ...

This won't find std::swap if you haven't done using std::swap or something similar. You should rather declare your own swap in C's namespace:

void swap(C &a, C &b) {
  a.swap(b);
}

Now, this will work also in the above case, because argument dependent lookup searches in the namespace of the class. Code swapping generic things where the type isn't known should do it like this:

using std::swap;
swap(a, b);

Regardless of the type, this will use the best matching swap, and fall-back to std::swap if there wasn't a better matching one in the namespaces of a. Hard-coding the call to std::swap will cut too short on types that don't specialize std::swap but rather decide to provide their own swap in their namespace.

This is superious in another way: Imagine C is a template. You cannot specialize std::swap in this case. But just defining your own swap, that's perfectly fine.

template<typename T>
void swap(C<T> &a, C<T> &b) {
  a.swap(b);
}

This is the way how the swap for std::string and other classes is implemented too.

Johannes Schaub - litb
The thing is, don't STL algorithms use std::swap? Such as std::reverse.
rlbond
I think it isn't specified whether std::swap is used or whether a unqualified call to "swap" is done (i would be glad for a reference on that, though). I looked, and gcc uses an unqualified call to "swap" - this will just work fine with the free swap function defined in the class' namespace. But i think any good Standard lib implementation won't call "std::swap" directly. That would be weird to do: It's already within "std", so qualification isn't necessary, and for another reason, it would stop argument dependent lookup working.
Johannes Schaub - litb
You're right. Section 20.1.4:The Swappable requirement is met by satisfying one or more of the following conditions:— T is Swappable if T satisfies the CopyConstructible requirments (20.1.3) and the Assignable requirements (23.1);— T is Swappable if a namespace scope function named swap exists in the same namespace as the definition of T, such that the expression swap(t,u) is valid and has the semantics described in Table 32.
rlbond
While I don't disagree with any of this, I will say it sucks that unlike any other function template in <algorithm>, std::swap alone should not be called by its full name just in case it's ADL overloaded. Stupid exceptions to the rule. I assume std::hash will be joining it, come C++0x.
Steve Jessop
@litb: It seems that Visual Studio's stl does make fully qualified calls to std::swap.
rlbond
Weird library, then, or broken ADL lookup of the compiler :) Maybe also a dependent name lookup issue of its compiler. Try including the `<algorithm>` (or whatever algorithm you use) header *after* the header defining your own `swap`, if not done already. Anyway, i think the above quote is from some C++1x draft? I don't find it in C++03 (section 20.1.4 doesn't exist in the latest draft n2914 either, though)
Johannes Schaub - litb
@onebyone : the "swap" is somewhat special in C++. when a particular swap is written with a "nothrow" exception guarantee, it can be used to enable stronger exception guarantees in user code while avoiding too much copying. This is the reason for the strange "call convention" of swap. In this, swap can be as special as a destructor or an operator =, and so, its special semantics must be known to use it best
paercebal
... Apparently, swap is so important boost features a standalone "boost::swap" enhanced version. For more info, please read: Scott Meyers, Effective C++ Third Edition, Item 25: "Consider support for a non-throwing swap"
paercebal
I have read Scott Meyers "Consider support for a non-throwing swap". I know why its desirable to overload swap, I'm just saying that it's inelegant to have one thing in `<algorithm>` be the odd one out. All the other algorithms are expected to be generic. For example unqualified find is not optimised for `std::set<X>::iterator`, although it probably could have been defined to be, just as unqualified swap is optimised for sets.
Steve Jessop
As you say, it boils down to the fact that swap is more like an operator with a default implementation, than it is like algorithms copy or binary_search. Its implementation depends heavily on the class, it's not generic at all. But there's no punctuation mark for it, so it can't be a true operator. And unlike auto-generated `operator=`, because it's an algorithm the default implementation doesn't even do field-by-field swap. Meh.
Steve Jessop
Discussed in ISO C++ Library Working Group. Libraries, including the Standard lib should call swap() as litb describes. This applies to more than just swap(). Libraries also cannot assume that they can call `u.operator<(v)` but must use the `u<v` form in case `operator<` is not a member.
MSalters