español c++ c++11 swap language-lawyer rationale

español - ¿Cambia C++ 11 el comportamiento de llamar explícitamente a std:: swap para garantizar que se encuentren swaps ubicados en ADL, como boost:: swap?



swap c++ español (3)

Fondo

Considera para esta pregunta el siguiente código:

#include <utility> namespace ns { struct foo { foo() : i(0) {} int i; private: foo(const foo&); // not defined, foo& operator=(const foo&); // non-copyable }; void swap(foo& lhs, foo& rhs) { std::swap(lhs.i, rhs.i); } } template <typename T> void do_swap(T& lhs, T& rhs); // implementation to be determined int main() { ns::foo a, b; do_swap(a, b); }

En C ++ 03, esta implementación de do_swap se consideraría "rota":

template <typename T> void do_swap(T& lhs, T& rhs) { std::swap(lhs, rhs); }

Al especificar explícitamente std:: , prohíbe que se encuentre ns::swap mediante la búsqueda dependiente del argumento. (Luego falla la compilación porque std::swap intenta copiar un foo , lo cual no está permitido). En su lugar, hacemos esto:

template <typename T> void do_swap(T& lhs, T& rhs) { using std::swap; // allow std::swap as a backup if ADL fails to find a swap swap(lhs, rhs); // unqualified call to swap, allow ADL to operate }

Ahora se encuentra ns::swap y no se usa std::swap , al ser menos especializado. Es más feo, pero funciona y es comprensible a simple vista. boost::swap envuelve esto muy bien para nosotros (y proporciona sobrecargas de matriz):

#include <boost/swap.hpp> template <typename T> void do_swap(T& lhs, T& rhs) { boost::swap(lhs, rhs); // internally does what do_swap did above }

Pregunta

Mi pregunta es : ¿toma std::swap el comportamiento de boost::swap en C ++ 11? Si no, ¿por qué?

A mí me parece obvio que debería hacerlo. Cualquier código roto por el cambio fue probablemente bastante débil en primer lugar (los algoritmos y los contenedores, como std::sort y std::vector , fueron subespecificados; se permitió que las implementaciones llamaran swaps de ADL o no de forma indeterminada), por lo que el cambio sería para el mejor. Además, std::swap ahora está definido para arreglos, por lo que el cambio no está fuera de discusión.

Sin embargo, mientras §17.6.3.2 especifica que todas las llamadas a swap dentro de la biblioteca estándar deben realizarse sin std:: qualification (solucionando el problema con los algoritmos y contenedores mencionados anteriormente), no se puede tocar en std::swap . Incluso da ejemplos de valores de intercambio que incluyen el using std::swap; . Del mismo modo, §20.2.2 (donde se especifica std::swap ) no dice una palabra en ADL.

Por último, GCC no habilita ADL en su implementación std::swap (tampoco lo hace MSVC, pero eso no dice mucho). Así que debo estar equivocado de que std::swap asume el comportamiento de boost::swap , pero no entiendo por qué no se realizó el cambio. :( y no estoy solo !


Aquí hay una implementación de prueba de concepto:

#include <utility> // exposition implementation namespace std_ { namespace detail { // actual fallback implementation template <typename T> void swap(T& lhs, T& rhs) { T temp = std::move(lhs); lhs = std::move(rhs); rhs = std::move(temp); } } template <typename T> void swap(T& lhs, T& rhs) { using detail::swap; // shadows std_::swap, stops recursion swap(lhs, rhs); // unqualified call, allows ADL } } namespace ns { struct foo { foo() : i(0) {} int i; private: foo(const foo&); // not defined, foo& operator=(const foo&); // non-copyable }; void swap(foo& lhs, foo& rhs) { std::swap(lhs.i, rhs.i); } } int main() { int i = 0, j = 0; std_::swap(i, j); ns::foo a, b; std_::swap(a, b); }


Bueno, boost::swap() envía a std::swap() . Para que std::swap() haga algo similar a boost::swap() , tendría que delegar en otro lugar. ¿Qué es esto en otro lugar? El estándar no exige otra versión de swap() que proporciona la implementación real. Esto se puede hacer, pero el estándar no lo exige.

¿Por qué no lo hace? No vi ninguna propuesta proponiendo esta implementación. Si alguien hubiera querido que esto se hiciera, estoy seguro de que habría sido propuesto.


Habría tenido que votar en contra de la implementación de la prueba de concepto si se hubiera propuesto. Me temo que rompería el siguiente código, que estoy bastante seguro de haber visto en la naturaleza al menos una o dos veces en los últimos doce años.

namespace oops { struct foo { foo() : i(0) {} int i; void swap(foo& x) {std::swap(*this, x);} }; void swap(foo& lhs, foo& rhs) { lhs.swap(rhs); } }

Ya sea que pienses que lo anterior es un código bueno o malo, funciona como lo pretende el autor en C ++ 98/03 y, por lo tanto, la barra para romperlo en silencio es bastante alta. Diciendo a los usuarios que en C ++ 11 ya no tendrían que escribir using std::swap; no es un beneficio suficientemente alto como para superar la desventaja de convertir silenciosamente el código anterior en una recursión infinita.

Otra forma de salir de la escritura using std::swap; es usar std::iter_swap en std::iter_swap lugar:

template <typename T> void do_swap(T& lhs, T& rhs) { std::iter_swap(&lhs, &rhs); // internally does what do_swap did above }