c++ c++11 gcc libstdc++ argument-dependent-lookup

c++ - ¿Está bien definir una función swap() totalmente general?



c++11 gcc (3)

El problema es la implementación de libstdc ++ de unique_ptr . Esto es de su rama 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 }

Como puede ver, hay una llamada de canje no calificada. Ahora veamos la implementación 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_);}

No llaman a swap dentro de reset ni usan una llamada de intercambio no calificada.

La respuesta de Dyp proporciona un desglose bastante sólido sobre por qué libstdc++ está adaptando, pero también explica por qué su código se romperá cada vez que se requiera el swap por parte de la biblioteca estándar. Para citar a TemplateRex :

No debería tener ninguna razón para definir una plantilla de swap general en un espacio de nombres muy específico que solo contenga tipos específicos. Simplemente defina una sobrecarga de swap no de plantilla para foo::bar . Deje el intercambio general a std::swap , y solo proporcione sobrecargas específicas. source

Como ejemplo, esto no compilará:

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

Si se dirige a una plataforma con una antigua biblioteca estándar / GCC (como CentOS), recomendaría usar Boost en lugar de reinventar la rueda para evitar trampas como esta.

El siguiente fragmento:

#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(); }

no compila para mí:

$ 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)

¿Es este mi error al declarar una función swap() tan general que entra en conflicto con std::swap ?

Si es así, ¿hay alguna manera de definir foo::swap() para que no sea arrastrada por la búsqueda de Koenig?


Esta técnica se puede usar para evitar que foo::swap() encontrado por 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; }

Así es como se definen las funciones free begin() / end() autónomas de Boost.Range. Intenté algo similar antes de hacer la pregunta, pero lo hice using adl_barrier::swap; en cambio, lo cual no funciona.

En cuanto a si el fragmento en la pregunta debería funcionar como está, no estoy seguro. Una complicación que puedo ver es que unique_ptr puede tener tipos de pointer personalizados del Deleter , que deberían intercambiarse con el using std::swap; swap(a, b); habitual de using std::swap; swap(a, b); using std::swap; swap(a, b); idioma. Ese modismo está claramente roto para foo::bar* en la pregunta.


  • unique_ptr<T> requiere que T* sea ​​un NullablePointer [unique.ptr] p3
  • NullablePointer requiere lvalues ​​de T* para ser Swappable [nullablepointer.requirements] p1
  • Swappable requiere esencialmente using std::swap; swap(x, y); using std::swap; swap(x, y); para seleccionar una sobrecarga para x , y siendo lvalues ​​de tipo T* [swappable.requirements] p3

En el último paso, su tipo foo::bar produce una ambigüedad y, por lo tanto, infringe los requisitos de unique_ptr . La implementación de libstdc ++ es conforme, aunque diría que esto es bastante sorprendente.

La redacción es, por supuesto, un poco más intrincada, porque es genérica.

[unique.ptr] p3

Si el tipo remove_reference_t<D>::pointer existe, entonces unique_ptr<T, D>::pointer será un sinónimo para remove_reference_t<D>::pointer . De unique_ptr<T, D>::pointer contrario unique_ptr<T, D>::pointer será un sinónimo de T* . El unique_ptr<T, D>::pointer debe cumplir los requisitos de NullablePointer .

(énfasis mío)

[nullablepointer.requirements] p1

Un tipo NullablePointer es un tipo de puntero que admite valores nulos. Un tipo P cumple los requisitos de NullablePointer si:

  • [...]
  • lvalues ​​de tipo P son intercambiables (17.6.3.2),
  • [...]

[swappable.requirements] p2

Un objeto t es intercambiable con un objeto u si y solo si:

  • las expresiones swap(t, u) y swap(u, t) son válidas cuando se evalúan en el contexto descrito a continuación, y
  • [...]

[swappable.requirements] p3

El contexto en el que se evalúan swap(t, u) y swap(u, t) garantizará que se seleccione una función binaria no miembro llamada "swap" mediante resolución de sobrecarga en un conjunto candidato que incluya:

  • las dos plantillas de función de swap definidas en <utility> y
  • el conjunto de búsqueda producido por búsqueda dependiente de argumento.

Tenga en cuenta que para un puntero escriba T* , a los efectos de ADL, los espacios de nombres y las clases asociadas se derivan del tipo T Por lo tanto, foo::bar* tiene foo como un espacio de nombres asociado. ADL para swap(x, y) donde x o y es un foo::bar* encontrará por lo tanto foo::swap .