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
existe, alors::pointer unique_ptr
doit être un synonyme pour::pointer remove_reference_t
. Sinon,::pointer unique_ptr
doit être un synonyme de::pointer T*
. Le typeunique_ptr
doit satisfaire aux exigences de::pointer NullablePointer
.
(emphase le mien)
[nullablepointer.requirements] p1
Un type
NullablePointer
est un type de type pointeur prenant en charge les valeurs NULL. Un typeP
répond aux exigences deNullablePointer
si:
- […]
- les lvalues de type
P
sont interchangeables (17.6.3.2),- […]
[swappable.requirements] p2
Un object
t
est échangeable avec un objectu
si et seulement si:
- les expressions
swap(t, u)
etswap(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)
etswap(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 danset
- 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)
où 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 ++):
_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 deswap
non-template pourfoo::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.