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 ++):
_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
swapgeneral en un espacio de nombres muy específico que solo contenga tipos específicos. Simplemente defina una sobrecarga deswapno de plantilla parafoo::bar. Deje el intercambio general astd::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 queT*sea unNullablePointer[unique.ptr] p3 -
NullablePointerrequiere lvalues deT*para serSwappable[nullablepointer.requirements] p1 -
Swappablerequiere esencialmenteusing std::swap; swap(x, y);using std::swap; swap(x, y);para seleccionar una sobrecarga parax,ysiendo lvalues de tipoT*[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>::pointerexiste, entoncesunique_ptr<T, D>::pointerserá un sinónimo pararemove_reference_t<D>::pointer. Deunique_ptr<T, D>::pointercontrariounique_ptr<T, D>::pointerserá un sinónimo deT*. Elunique_ptr<T, D>::pointerdebe cumplir los requisitos deNullablePointer.
(énfasis mío)
[nullablepointer.requirements] p1
Un tipo
NullablePointeres un tipo de puntero que admite valores nulos. Un tipoPcumple los requisitos deNullablePointersi:
- [...]
- lvalues de tipo
Pson intercambiables (17.6.3.2),- [...]
[swappable.requirements] p2
Un objeto
tes intercambiable con un objetousi y solo si:
- las expresiones
swap(t, u)yswap(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)yswap(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
swapdefinidas 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 .