Est-il correct de définir une fonction swap () totalement générale?

L’extrait suivant:

#include  #include  namespace foo { template  void swap(T& a, T& b) { T tmp = std::move(a); a = std::move(b); b = std::move(tmp); } struct bar { }; } void baz() { std::unique_ptr ptr; ptr.reset(); } 

ne comstack pas pour moi:

 $ g++ -std=c++11 -c foo.cpp In file included from /usr/include/c++/5.3.0/memory:81:0, from foo.cpp:1: /usr/include/c++/5.3.0/bits/unique_ptr.h: In instantiation of 'void std::unique_ptr::reset(std::unique_ptr::pointer) [with _Tp = foo::bar; _Dp = std::default_delete; std::unique_ptr::pointer = foo::bar*]': foo.cpp:20:15: required from here /usr/include/c++/5.3.0/bits/unique_ptr.h:342:6: error: call of overloaded 'swap(foo::bar*&, foo::bar*&)' is ambiguous swap(std::get(_M_t), __p); ^ In file included from /usr/include/c++/5.3.0/bits/stl_pair.h:59:0, from /usr/include/c++/5.3.0/bits/stl_algobase.h:64, from /usr/include/c++/5.3.0/memory:62, from foo.cpp:1: /usr/include/c++/5.3.0/bits/move.h:176:5: note: candidate: void std::swap(_Tp&, _Tp&) [with _Tp = foo::bar*] swap(_Tp& __a, _Tp& __b) ^ foo.cpp:7:10: note: candidate: void foo::swap(T&, T&) [with T = foo::bar*] void swap(T& a, T& b) 

Est-ce ma faute pour déclarer une fonction swap() si générale qu’elle est en conflit avec std::swap ?

Si oui, existe-t-il un moyen de définir foo::swap() pour qu’il ne soit pas transporté par la recherche Koenig?

  • unique_ptr nécessite que T* soit un NullablePointer [unique.ptr] p3
  • NullablePointer nécessite que les valeurs de T* soient Swappable [nullablepointer.requirements] p1
  • Swappable nécessite essentiellement l’ using std::swap; swap(x, y); using std::swap; swap(x, y); pour sélectionner une surcharge pour x , y étant des valeurs de type T* [swappable.requirements] p3

Dans la dernière étape, votre type foo::bar produit une ambiguïté et viole donc les exigences de unique_ptr . L’implémentation de libstdc ++ est conforme, même si je dirais que c’est plutôt surprenant.


La formulation est évidemment un peu plus compliquée, car elle est générique.

[unique.ptr] p3

Si le type remove_reference_t::pointer existe, alors unique_ptr::pointer doit être un synonyme pour remove_reference_t::pointer . Sinon, unique_ptr::pointer doit être un synonyme de T* . Le type unique_ptr::pointer doit satisfaire aux exigences de NullablePointer .

(emphase le mien)

[nullablepointer.requirements] p1

Un type NullablePointer est un type de type pointeur prenant en charge les valeurs NULL. Un type P répond aux exigences de NullablePointer si:

  • […]
  • les lvalues ​​de type P sont interchangeables (17.6.3.2),
  • […]

[swappable.requirements] p2

Un object t est échangeable avec un object u si et seulement si:

  • les expressions swap(t, u) et swap(u, t) sont valides lorsqu’elles sont évaluées dans le contexte décrit ci-dessous, et
  • […]

[swappable.requirements] p3

Le contexte dans lequel swap(t, u) et swap(u, t) sont évalués doit garantir qu’une fonction non-membre binary appelée «swap» est sélectionnée via une résolution de surcharge sur un ensemble candidat comprenant:

  • les deux modèles de fonction de swap définis dans et
  • l’ensemble de recherche produit par la recherche dépendante des arguments.

Notez que pour un pointeur de type T* , aux fins de la liste ADL, les espaces de noms et les classes associés sont dérivés du type T Par conséquent, foo::bar* a foo comme espace de noms associé. ADL pour swap(x, y)x ou y est un foo::bar* trouvera donc foo::swap .

Le problème est l’implémentation de libstdc ++ de unique_ptr . Ceci provient de leur twig 4.9.2:

https://gcc.gnu.org/onlinedocs/gcc-4.9.2/libstdc++/api/a01298_source.html#l00339

  338 void 339 reset(pointer __p = pointer()) noexcept 340 { 341 using std::swap; 342 swap(std::get<0>(_M_t), __p); 343 if (__p != pointer()) 344 get_deleter()(__p); 345 } 

Comme vous pouvez le voir, il existe un appel de swap sans réserve. Voyons maintenant l’implémentation de libcxx (libc ++):

https://git.io/vKzhF

 _LIBCPP_INLINE_VISIBILITY void reset(pointer __p = pointer()) _NOEXCEPT { pointer __tmp = __ptr_.first(); __ptr_.first() = __p; if (__tmp) __ptr_.second()(__tmp); } _LIBCPP_INLINE_VISIBILITY void swap(unique_ptr& __u) _NOEXCEPT {__ptr_.swap(__u.__ptr_);} 

Ils n’appellent pas swap dedans reset ni n’utilisent un appel d’échange non qualifié.


La réponse de Dyp fournit une ventilation assez détaillée de la raison pour laquelle libstdc++ est conforme, mais aussi de la raison pour laquelle votre code va se briser à chaque fois que la bibliothèque standard doit appeler swap . Pour citer TemplateRex :

Vous ne devriez pas avoir de raison de définir un tel modèle d’ swap dans un espace de noms très spécifique contenant uniquement des types spécifiques. Il suffit de définir une surcharge de swap non-template pour foo::bar . Laissez l’échange général à std::swap et ne fournissez que des surcharges spécifiques. la source

Par exemple, cela ne comstackra pas:

 std::vector v; std::vector().swap(v); 

Si vous ciblez une plate-forme avec une ancienne bibliothèque standard / GCC (comme CentOS), je vous recommande d’utiliser Boost au lieu de réinventer la roue pour éviter les pièges comme celui-ci.

Cette technique peut être utilisée pour éviter que foo::swap() ne soit trouvé par ADL:

 namespace foo { namespace adl_barrier { template  void swap(T& a, T& b) { T tmp = std::move(a); a = std::move(b); b = std::move(tmp); } } using namespace adl_barrier; } 

C’est ainsi que les fonctions autonomes de begin() / end() Boost.Range sont définies. J’ai essayé quelque chose de similaire avant de poser la question, mais using adl_barrier::swap; au lieu de cela, cela ne fonctionne pas.

Quant à savoir si l’extrait dans la question devrait fonctionner tel quel, je ne suis pas sûr. Une complication que je peux voir est que unique_ptr peut avoir des types de pointer personnalisés à partir du Deleter , qui doivent être échangés avec les habituels en using std::swap; swap(a, b); using std::swap; swap(a, b); idiome. Cet idiome est clairement cassé pour foo::bar* dans la question.