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
swap
general en un espacio de nombres muy específico que solo contenga tipos específicos. Simplemente defina una sobrecarga deswap
no 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 -
NullablePointer
requiere lvalues deT*
para serSwappable
[nullablepointer.requirements] p1 -
Swappable
requiere esencialmenteusing std::swap; swap(x, y);
using std::swap; swap(x, y);
para seleccionar una sobrecarga parax
,y
siendo 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>::pointer
existe, entoncesunique_ptr<T, D>::pointer
será un sinónimo pararemove_reference_t<D>::pointer
. Deunique_ptr<T, D>::pointer
contrariounique_ptr<T, D>::pointer
será un sinónimo deT*
. Elunique_ptr<T, D>::pointer
debe cumplir los requisitos deNullablePointer
.
(énfasis mío)
[nullablepointer.requirements] p1
Un tipo
NullablePointer
es un tipo de puntero que admite valores nulos. Un tipoP
cumple los requisitos deNullablePointer
si:
- [...]
- lvalues de tipo
P
son intercambiables (17.6.3.2),- [...]
[swappable.requirements] p2
Un objeto
t
es intercambiable con un objetou
si 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
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
.