¿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?

Author: Tavian Barnes, 2016-04-25

3 answers

  • unique_ptr<T> requiere que T* sea un NullablePointer [único.ptr] p3
  • NullablePointer requiere que lvalues de T* sea Swappable [nullablepointer.requisitos] p1
  • Swappable esencialmente requiere using std::swap; swap(x, y); para seleccionar una sobrecarga para x, y siendo lvalues de tipo T* [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, entonces unique_ptr<T, D>::pointer será sinónimo de remove_reference_t<D>::pointer. De lo contrario, unique_ptr<T, D>::pointer será sinónimo de T*. El tipo unique_ptr<T, D>::pointer deberá cumplir los requisitos de NullablePointer.

(énfasis mío)

[nullablepointer.requisitos] p1

A NullablePointer el tipo es a tipo tipo puntero que admite null valor. Un tipo P cumple los requisitos de NullablePointer si:

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

[intercambiable.requisitos] 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
  • [...]

[intercambiable.requisitos] p3

El contexto en el que se evalúan swap(t, u) y swap(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.

 25
Author: dyp,
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++):

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 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-plantilla swap para foo::bar. Dejar el intercambio general a std::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.

 14
Author: user6253369,
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.

 12
Author: Tavian Barnes,
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