39 votes

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

L'extrait suivant:

 #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 si j'ai déclaré une fonction swap() si générale qu'elle est en conflit avec std::swap ?

Si tel est le cas, existe-t-il un moyen de définir foo::swap() sorte qu'il ne soit pas attiré par la recherche Koenig?

25voto

dyp Points 19641
  • unique_ptr<T> exige T* un NullablePointer [unique.ptr]p3
  • NullablePointer nécessite lvalues d' T* être Swappable [nullablepointer.exigences]p1
  • Swappable nécessite essentiellement des using std::swap; swap(x, y); pour sélectionner une surcharge pour l' x, y étant lvalues de type T* [remplaçables à chaud.exigences]p3

Dans la dernière étape, votre type foo::bar produit une ambiguïté et, par conséquent, viole les exigences de l' unique_ptr. libstdc++de la mise en œuvre est conforme, même si je dirais que c'est plutôt surprenant.


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

[unique.ptr]p3

Si le type remove_reference_t<D>::pointerexiste, ensuite, unique_ptr<T, D>::pointer doit être synonyme de remove_reference_t<D>::pointer. Sinon, unique_ptr<T, D>::pointer doit être synonyme d' T*. Le type unique_ptr<T, D>::pointer doivent satisfaire aux exigences de l' NullablePointer.

(l'emphase est mienne)

[nullablepointer.exigences]p1

Un NullablePointer type est un pointeur de type qui prend en charge null des valeurs. Un type P satisfait aux exigences de l' NullablePointer si:

  • [...]
  • lvalues de type P sont remplaçables (17.6.3.2),
  • [...]

[remplaçables à chaud.exigences]p2

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

  • les expressions swap(t, u) et swap(u, t) sont valides au moment de l'évaluation dans le contexte décrit ci-dessous, et
  • [...]

[remplaçables à chaud.exigences]p3

Le contexte dans lequel s' swap(t, u) et swap(u, t) sont évaluées doit s'assurer qu'un binaire non-fonction membre nommée "swap" est sélectionné par le biais résolution de surcharge sur un ensemble candidat qui comprend:

  • les deux swap les modèles de fonction définie en <utility> et
  • la recherche produite par l'argument-dépendante de recherche.

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

14voto

user6253369 Points 141

Le problème est libstdc++de mise en œuvre de l' unique_ptr. C'est à partir de leurs 4.9.2 branche:

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 non qualifiés swaps appel. Maintenant, nous allons voir libcxx (libc++) 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 d' reset ni n'en font pas de diplômes swap appel.


Dpj réponse fournit une bonne ventilation sur pourquoi est - libstdc++ est conforme, mais aussi pourquoi votre code de pause à chaque fois qu' swap est nécessaire pour être appelé par la bibliothèque standard. Pour citer TemplateRex:

Vous ne devriez pas avoir de raison de le définir tel un général swap modèle dans un très espace de noms spécifique contenant uniquement des types spécifiques. Il suffit de définir un non-modèle d' swap de surcharge pour foo::bar. Laisser général de permutation d' std::swap, et seulement de fournir des surcharges. source

Comme un exemple, ce ne compile pas:

std::vector<foo::bar> v;
std::vector<foo::bar>().swap(v);

Si vous ciblez une plate-forme avec un ancien de la bibliothèque standard/GCC (comme CentOS), je recommande l'utilisation de Boost au lieu de réinventer la roue pour éviter les pièges de ce genre.

12voto

Tavian Barnes Points 2899

Cette technique peut être utilisée pour éviter foo::swap() se faire découvrir 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;
}

C'est de cette façon Boost.Gamme de libre-debout begin()/end() fonctions sont définies. J'ai essayé quelque chose de similaire avant de poser la question, mais avez - using adl_barrier::swap; au lieu de cela, ce qui ne fonctionne pas.

Quant à savoir si l'extrait de code dans la question devrait fonctionner comme-est, je ne suis pas sûr. Une complication que je peux voir, c'est qu' unique_ptr personnalisées peuvent être pointer types de l' Deleter, ce qui devrait être échangé avec l'habituel using std::swap; swap(a, b); idiome. Cet idiome est clairement cassé pour foo::bar* dans la question.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X