2016-04-25 1 views
39

L'extrait suivant:Est-il acceptable de définir une fonction swap() totalement générale?

#include <memory> 
#include <utility> 

namespace foo 
{ 
    template <typename T> 
    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<foo::bar> ptr; 
    ptr.reset(); 
} 

ne compile 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<_Tp, _Dp>::reset(std::unique_ptr<_Tp, _Dp>::pointer) [with _Tp = foo::bar; _Dp = std::default_delete<foo::bar>; std::unique_ptr<_Tp, _Dp>::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<0>(_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() afin qu'il ne soit pas importé par Koenig lookup?

+0

Ne compile pas sur GCC ou Clang, mais compile sur MSVC 2015. Une autre fonctionnalité non documentée peut-être. – wally

+0

Putain, ce sont de bons ratios en moins de la première heure. +16 upvotes, visualisés 77 fois, et 4 favoris. –

+1

Vous ne devriez avoir aucune raison de définir un tel modèle de 'swap' général dans un espace de noms très spécifique contenant seulement des types spécifiques. Définissez simplement une surcharge swap non-template pour 'foo :: bar'. Laissez l'échange général sur 'std :: swap' et ne fournissez que des surcharges spécifiques. – TemplateRex

Répondre

25
  • unique_ptr<T> nécessite T* être un NullablePointer [unique.ptr] p3
  • NullablePointer nécessite lvalues ​​de T* être Swappable [nullablepointer.requirements] p1
  • Swappable nécessite essentiellement using std::swap; swap(x, y); pour sélectionner une surcharge pour x, y étant des lvalues ​​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.


Le libellé est bien sûr un peu plus compliqué, car il est générique.

[unique.ptr] p3

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

(non souligné)

[nullablepointer.requirements] p1

Un type NullablePointer est un type de pointeur en forme qui prend en charge les valeurs nulles . Un type P satisfait aux exigences de NullablePointer si:

  • [...]
  • lvalues ​​de type P sont permutables (17.6.3.2),
  • [...]

[permutable.exigences] p2

Un objet t est swappable avec un objet u si et seulement si:

  • les expressions swap(t, u) et swap(u, t) sont valides lorsqu'ils ont été évalués 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 assurer qu'une fonction non-membre binaire « swap » est sélectionné par résolution de surcharge sur un ensemble candidat qui comprend:

  • les deux swap modèles de fonction défini dans <utility> et
  • l'ensemble de recherche produit par la recherche dépendant de l'argument.

Notez que pour un type de pointeur T*, à des fins d'ADL, les espaces de noms et classes associées sont dérivées du type T. Par conséquent, foo::bar* a foo en tant qu'espace de noms associé. ADL pour swap(x, y)x ou y est foo::bar* va donc trouver foo::swap.

+0

Vous pouvez trouver une répartition plus simple [ici] (http://fr.cppreference.com/w/cpp/concept/Swappable) pour ceux qui ne parlent pas avocat. Quoiqu'il en soit, alors que libstdC++ suit certainement les règles ici, il ne semble pas y avoir besoin d'utiliser le swap ADL dans la réinitialisation, étant donné que le swap des deleters de libcxx est fonctionnellement équivalent. Je vais redire cela à la qualité de la mise en œuvre. – user6253369

+0

@ user6253369 c'est QoI sur la partie OP ici. Il suffit de ne pas fournir de modèles généraux dupliquant les points de personnalisation des modèles STL, et surtout ne fournissez pas vos propres classes à ces modèles STL lorsque vous le faites. – TemplateRex

+1

@ user6253369 Pour les pointeurs sophistiqués qui définissaient réellement des swaps personnalisés, l'utilisation de ceux-ci peut s'avérer plus efficace. –

12

Cette technique peut être utilisée pour éviter foo::swap() se trouve par ADL:

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

    using namespace adl_barrier; 
} 

Voici comment les fonctions autonomes de Boost.Range begin()/end() sont définis. J'ai essayé quelque chose de similaire avant de poser la question, mais a la place using adl_barrier::swap;, qui ne fonctionne pas.

Quant à savoir si doit travailler l'extrait dans la question-est, je ne suis pas sûr. Une complication que je peux voir est que unique_ptr peut avoir sur mesure pointer types de la Deleter, qui devrait être échangé avec l'idiome using std::swap; swap(a, b); habituelle. Cet idiome est clairement cassé pour foo::bar* dans la question.

+1

Si c'est "totalement général", alors pourquoi ne pas simplement importer 'std :: swap' dans' foo' avec une déclaration using? – dyp

+1

@dyp 'foo :: swap()' a été écrit parce qu'une de nos plates-formes a une bibliothèque standard avec un 'std :: swap()' déficient (fait des copies plutôt que des déplacements). Je ferai probablement 'en utilisant std :: swap;' sauf sur cette plate-forme. –

+0

btw, je pense qu'il est préférable de protéger votre type ADL (ie 'foo :: bar') à la place de vos fonctions (ie' foo :: swap'), puisque ce sont elles qui causent le chagrin, et le nombre de fonctions dans 'namespace foo' est open-ended (quelqu'un pourrait ajouter' foo :: begin' et 'foo :: end' un jour et vous aurez de nouveaux problèmes, les' 'foo :: bar' protégés par ADL contre ça). – TemplateRex

14

Le problème est libstdc la mise en œuvre de ++ de unique_ptr. Ceci est de leur branche 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 y a un appel d'échange sans réserve. Maintenant, nous allons voir libcxx (libC++) la mise en œuvre »:

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 ne remettent pas swap à l'intérieur, ni reset utilisent-ils un appel d'échange sans réserve.


Dyp's answer fournit une ventilation assez solide pourquoi libstdc++ est conforme mais aussi pourquoi votre code brisera chaque fois swap doit être appelé par la bibliothèque standard. Pour citer TemplateRex:

Vous devriez avoir aucune raison de définir un modèle swap général dans un espace de noms très spécifique contenant seulement les types spécifiques. Il suffit de définir un non-modèle swap surcharge pour foo::bar. Laissez général échanger à std::swap, et ne fournissent que des surcharges spécifiques.source

À titre d'exemple, ce ne sera pas compilé:

std::vector<foo::bar> v; 
std::vector<foo::bar>().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.

+0

"Je recommande d'utiliser Boost au lieu de réinventer la roue" D'accord! Nous faisons cela pour beaucoup de choses dans 'foo'. Malheureusement, boost :: swap() 'ne fait pas les assignations de déplacement, mais délègue simplement' std :: swap'. –

+0

@TavianBarnes Cela peut être vrai mais je crois que l'équivalent Boost de unique_ptr utilise la sémantique de déplacement pour swap par exemple. – user6253369

+0

À droite, mais je veux souvent faire 'swap (a, b);' où 'a' et' b' sont d'un type non-copiable. C'est une douleur de déclarer une surcharge pour * chaque * type de déplacement que je définis, donc j'ai besoin d'une implémentation 'swap()' qui bouge. –