¿Está bien definir una función swap() totalmente general?
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 esto mi culpa por 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 arrastrado por la búsqueda de Koenig?
3 answers
-
unique_ptr<T>
requiere queT*
sea unNullablePointer
[único.ptr] p3 -
NullablePointer
requiere que lvalues deT*
seaSwappable
[nullablepointer.requisitos] p1 -
Swappable
esencialmente requiereusing std::swap; swap(x, y);
para seleccionar una sobrecarga parax
,y
siendo lvalues de tipoT*
[intercambiable.requisitos] p3
En el último paso, su tipo foo::bar
produce una ambigüedad y, por lo tanto, viola los requisitos de unique_ptr
. la implementación de libstdc++es conforme, aunque yo diría que esto es más bien sorprendente.
La redacción es, por supuesto, un poco más complicada, porque es genérica.
[único.ptr] p3
Si el tipo
remove_reference_t<D>::pointer
existe, entoncesunique_ptr<T, D>::pointer
será sinónimo deremove_reference_t<D>::pointer
. De lo contrario,unique_ptr<T, D>::pointer
será sinónimo deT*
. El tipounique_ptr<T, D>::pointer
deberá cumplir los requisitos deNullablePointer
.
(énfasis mío)
[nullablepointer.requisitos] p1
A
NullablePointer
el tipo es a tipo tipo puntero que admite null valor. Un tipoP
cumple los requisitos deNullablePointer
si:
- [...]
- los lvalues de tipo
P
son intercambiables (17.6.3.2),- [...]
[intercambiable.requisitos] 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- [...]
[intercambiable.requisitos] p3
El contexto en el que se evalúan
swap(t, u)
yswap(u, t)
deberá asegúrese de que una función binaria no miembro llamada "swap" esté seleccionada a través de resolución de sobrecarga en un conjunto candidato que incluye:
- las dos plantillas de función
swap
definidas en<utility>
y- el conjunto de búsqueda producido por la búsqueda dependiente de argumentos.
Tenga en cuenta que para un puntero escriba T*
, para los propósitos de ADL, los espacios de nombres asociados y las clases se derivan del tipo T
. Por lo tanto, foo::bar*
tiene foo
como un espacio de nombres asociado. ADL for swap(x, y)
where either x
or y
is a foo::bar*
will therefore find foo::swap
.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-04-25 21:16:19
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 swap 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 swap
dentro de reset
ni usan una llamada swap no calificada.
Dyp's answer proporciona un desglose bastante sólido sobre por qué libstdc++
se está conformando, pero también por qué su código se romperá cada vez que swap
sea requerido para ser llamado por la biblioteca estándar. Para citar TemplateRex:
No debería tener ninguna razón para definir tal plantilla general
swap
en un espacio de nombres muy específico que contiene solo tipos específicos. Sólo define una sobrecarga no-plantillaswap
parafoo::bar
. Dejar el intercambio general astd::swap
, y solo proporcionan sobrecargas específicas. fuente
Como ejemplo, esto no compilará:
std::vector<foo::bar> v;
std::vector<foo::bar>().swap(v);
Si estás apuntando a una plataforma con una antigua biblioteca estándar/GCC (como CentOS), te recomendaría usar Boost en lugar de reinventar la rueda para evitar errores como este.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 11:51:32
Esta técnica se puede utilizar para evitar foo::swap()
ser 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 Boost.La gama es independiente begin()
/end()
las funciones están definidas. Probé algo similar antes de hacer la pregunta, pero lo hice using adl_barrier::swap;
en su lugar, lo cual no funciona.
En cuanto a si el fragmento de código en la pregunta debería funcionar tal cual, no estoy seguro. Una complicación que puedo ver es que unique_ptr
puede tener tipos personalizados pointer
del Deleter
, que deben intercambiarse con el idioma habitual using std::swap; swap(a, b);
. Que el modismo está claramente roto para foo::bar*
en la pregunta.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-04-25 20:54:19