Specializing std::swap

Hi,

It was stated in this bug report

http://llvm.org/bugs/show_bug.cgi?id=10248

that specializing swap for non-std types in std namespace, although
legal, is considered fragile. It is not entirely clear from the
response why is that so.

Can someone shed some light on this?

Thanks in advance,

Alex

I'm not sure this is topical for cfe-dev, as it's just a question about C++.

C++ doesn't have partial specialization for templates, so it's not possible
to define a std::swap for a user-define template. You could write an
overload, except that it's not permitted to add overloads to namespace std.

On the bright side, ADL means that it's not necessary to put those in
namespace std anyway, so long as people use swap properly (such that ADL is
enabled).

-- James

Thanks for prompt response.

I'm not sure this is topical for cfe-dev, as it's just a question about C++.

I'm not sure, either and I apologize if this is out of place. If
someone can point me to the right place (or continue private
discussion) I'd be happy to do that. I just never had problem with
other compilers, so I thought I'd ask here for clarification.

C++ doesn't have partial specialization for templates,

I guess you meant for functions?

so it's not possible
to define a std::swap for a user-define template.

Poco::Data::Statement is not a template.

The question is about full specialization, which is explicitly permitted:

17.4.3.1 Reserved names [lib.reserved.names]
1 ... A program may add template specializations for any standard
library template to namespace std. Such a specialization (complete or
partial) of a standard library template results in undefined behavior
unless the declaration depends on a user-defined type of external
linkage and unless the specialization meets the standard library
requirements for the original template.
...

You could write an
overload, except that it's not permitted to add overloads to namespace std.

I am aware of that. That's why it is specialized.

On the bright side, ADL means that it's not necessary to put those in
namespace std anyway, so long as people use swap properly (such that ADL is
enabled).

I understand there is a workaround, but if someone explicitly calls
std::swap<Statement>, then my swap will not be called.

I'm interested in a generic answer to the question: why is
specializing standard templates (such as e.g. std::swap) not a good
practice?

Again, thanks and if there is a better place for this discussion
please point me there.

Alex

[cfe-dev now on BCC.]

Although there is no good reason to specialized swap in std namespace, that was not the major problem. The major problem was how swap was called:

  std::swap<Poco::Data::Statement>(s1, s2);

I.e. specifying the template argument.

This will match any std-defined swap with the following signature:

    template <class T> void swap(anything-at-all);

This causes the *signatures*, not the implementations of all of the following to be instantiated:

template <> void swap(__bit_reference<Poco::Data::Statement> __x, __bit_reference<Poco::Data::Statement> __y) _NOEXCEPT;
template <> void swap(shared_ptr< Poco::Data::Statement >& __x, shared_ptr< Poco::Data::Statement >& __y) _NOEXCEPT;
template <> void swap(weak_ptr<Poco::Data::Statement>& __x, weak_ptr<Poco::Data::Statement>& __y) _NOEXCEPT;
template <> void swap(unique_lock<Poco::Data::Statement>& __x, unique_lock<Poco::Data::Statement>& __y);

etc.

The first signature instantiation listed above is actually the trouble maker. But the rest are questionable, especially the one involving unique_lock. The template parameter for unique_lock should meet the BasicLockable requirements. It is almost certain that Poco::Data::Statement is not a BasicLockable type.

The danger is that simply instantiating a function signature for swap can lead to a compile time error. Maybe unique_lock<T> requires a typedef that Poco::Data::Statement doesn't have? E.g: unique_lock<BasicLockable> requires BasicLockable::is_lock_free to exist. This isn't the case. But this is exactly what happened with __bit_reference<Poco::Data::Statement>:

/usr/include/c++/v1/__bit_reference:26:26: error: no type named '__storage_type' in 'Poco::Data::Statement'
    typedef typename _C::__storage_type __storage_type;
            ~~~~~~~~~~~~~^~~~~~~~~~~~~~

Some day some std-defined type X will get defined that requires some typedef (value_type, difference_type, iterator_category, whatever), that also defines a template <classT> swap(X<T>&, X<T>&), and it will break all code that calls std::swap<MyType>(...).

The cure is two-fold:

1. Don't specify the template argument when you call swap. Just call swap(x, y).

2. Don't qualify namespace std when you call swap unless you *know* that you need to call a swap in namespace std. If you don't know, issue a using std::swap first, to let ADL work its magic.

The first rule is more important than the second.

Here is the libc++ diff that fixed this problem. All that was done was to to create a fake __bit_reference<T> that did not require T to have any nested typedefs.

Index: include/bitset

Although there is no good reason to specialized swap in std namespace, that was not the major problem. The major problem was how swap was called:

  std::swap<Poco::Data::Statement>(s1, s2);

I.e. specifying the template argument.

This will match any std-defined swap with the following signature:

   template <class T> void swap(anything-at-all);

This causes the *signatures*, not the implementations of all of the following to be instantiated:

template <> void swap(__bit_reference<Poco::Data::Statement> __x, __bit_reference<Poco::Data::Statement> __y) _NOEXCEPT;
template <> void swap(shared_ptr< Poco::Data::Statement >& __x, shared_ptr< Poco::Data::Statement >& __y) _NOEXCEPT;
template <> void swap(weak_ptr<Poco::Data::Statement>& __x, weak_ptr<Poco::Data::Statement>& __y) _NOEXCEPT;
template <> void swap(unique_lock<Poco::Data::Statement>& __x, unique_lock<Poco::Data::Statement>& __y);

etc.

The first signature instantiation listed above is actually the trouble maker. But the rest are questionable, especially the one involving unique_lock. The template parameter for unique_lock should meet the BasicLockable requirements. It is almost certain that Poco::Data::Statement is not a BasicLockable type.

The danger is that simply instantiating a function signature for swap can lead to a compile time error. Maybe unique_lock<T> requires a typedef that Poco::Data::Statement doesn't have? E.g: unique_lock<BasicLockable> requires BasicLockable::is_lock_free to exist. This isn't the case. But this is exactly what happened with __bit_reference<Poco::Data::Statement>:

/usr/include/c++/v1/__bit_reference:26:26: error: no type named '__storage_type' in 'Poco::Data::Statement'
   typedef typename _C::__storage_type __storage_type;
           ~~~~~~~~~~~~~^~~~~~~~~~~~~~

Some day some std-defined type X will get defined that requires some typedef (value_type, difference_type, iterator_category, whatever), that also defines a template <classT> swap(X<T>&, X<T>&), and it will break all code that calls std::swap<MyType>(...).

The cure is two-fold:

1. Don't specify the template argument when you call swap. Just call swap(x, y).

2. Don't qualify namespace std when you call swap unless you *know* that you need to call a swap in namespace std. If you don't know, issue a using std::swap first, to let ADL work its magic.

The first rule is more important than the second.

Here is the libc++ diff that fixed this problem. All that was done was to to create a fake __bit_reference<T> that did not require T to have any nested typedefs.

Index: include/bitset

Thanks, Howard - that was the explanation I was looking for.

Alex