tag manager instalar google c++ c++11 iterator containers argument-dependent-lookup

c++ - manager - ¿Deben los contenedores personalizados tener funciones gratuitas de inicio/finalización?



instalar google tag manager (1)

Al crear una clase de contenedor personalizado que se rige por las reglas habituales (es decir, funciona con algoritmos STL, funciona con código genérico de buen comportamiento, etc.), en C ++ 03 fue suficiente implementar soporte de iterador y funciones de inicio / finalización de miembro.

C ++ 11 presenta dos nuevos conceptos: basado en rango para loop y std :: begin / end. Range-based for loop entiende las funciones de inicio / finalización de los miembros, por lo que cualquier contenedor C ++ 03 es compatible con el rango de forma inmediata. Para los algoritmos, la forma recomendada (de acuerdo con "Escribir código C ++ moderno" de Herb Sutter) es usar std :: begin en lugar de función de miembro.

Sin embargo, en este punto tengo que preguntar: ¿es la forma recomendada de llamar a una función totalmente calificada de begin () (es decir, std :: begin (c)) o confiar en ADL y llamar a begin (c)?

La ADL parece inútil en este caso particular, ya que std :: begin (c) delega en c.begin () si es posible, los beneficios de ADL habituales no parecen aplicarse. Y si todo el mundo comienza a confiar en ADL, todos los contenedores personalizados deben implementar funciones gratuitas de inicio () / end () adicionales en sus espacios de nombres requeridos. Sin embargo, varias fuentes parecen implicar que las llamadas no calificadas para comenzar / finalizar son la forma recomendada (es decir, https://svn.boost.org/trac/boost/ticket/6357 ).

Entonces, ¿cuál es el camino de C ++ 11? ¿Deberían los autores de bibliotecas de contenedores escribir funciones adicionales de inicio / finalización para sus clases para admitir llamadas de inicio / finalización no calificadas en ausencia de utilizar el espacio de nombres estándar? o usando std :: begin ;?


Hay varios enfoques, cada uno con sus propios pros y contras. Por debajo de tres enfoques con un análisis de costo-beneficio.

ADL a través de un no miembro personalizado begin() / end()

La primera alternativa proporciona plantillas de funciones non-member begin() y end() dentro de un espacio de nombres legacy para actualizar la funcionalidad requerida en cualquier clase o plantilla de clase que pueda proporcionarla, pero tiene, por ejemplo, las convenciones de nomenclatura incorrectas. El código de llamada puede entonces confiar en ADL para encontrar estas nuevas funciones. Código de ejemplo (basado en los comentarios de @Xeo):

// LegacyContainerBeginEnd.h namespace legacy { // retro-fitting begin() / end() interface on legacy // Container class template with incompatible names template<class C> auto begin(Container& c) -> decltype(c.legacy_begin()) { return c.legacy_begin(); } // similarly for begin() taking const&, cbegin(), end(), cend(), etc. } // namespace legacy // print.h template<class C> void print(C const& c) { // bring into scope to fall back on for types without their own namespace non-member begin()/end() using std::begin; using std::end; // works for Standard Containers, C-style arrays and legacy Containers std::copy(begin(c), end(c), std::ostream_iterator<decltype(*begin(c))>(std::cout, " ")); std::cout << "/n"; // alternative: also works for Standard Containers, C-style arrays and legacy Containers for (auto elem: c) std::cout << elem << " "; std::cout << "/n"; }

Pros : convención de llamadas coherentes y consistentes que funciona de manera completamente genérica

  • funciona para cualquier contenedor estándar y tipos de usuario que definan miembros .begin() y .end()
  • funciona para arreglos de estilo C
  • se puede actualizar para que funcione (¡también para bucles range-for !) para cualquier plantilla de clase legacy::Container<T> que no tenga miembro .begin() y end() sin requerir modificaciones en el código fuente

Contras : requiere usar declaraciones en muchos lugares

  • std::begin y std::end deben haberse incluido en todos los ámbitos explícitos de las llamadas como opciones de repliegue para las matrices de estilo C (posibles inconvenientes para los encabezados de las plantillas y molestias generales)

ADL a través de no miembro personalizado adl_begin() y adl_end()

Una segunda alternativa es encapsular las declaraciones de uso de la solución anterior en un espacio de nombres de adl separado al proporcionar plantillas de funciones no miembro adl_begin() y adl_end() , que también se pueden encontrar a través de ADL. Código de ejemplo (basado en los comentarios de @Yakk):

// LegacyContainerBeginEnd.h // as before... // ADLBeginEnd.h namespace adl { using std::begin; // <-- here, because otherwise decltype() will not find it template<class C> auto adl_begin(C && c) -> decltype(begin(std::forward<C>(c))) { // using std::begin; // in C++14 this might work because decltype() is no longer needed return begin(std::forward<C>(c)); // try to find non-member, fall back on std:: } // similary for cbegin(), end(), cend(), etc. } // namespace adl using adl::adl_begin; // will be visible in any compilation unit that includes this header // print.h # include "ADLBeginEnd.h" // brings adl_begin() and adl_end() into scope template<class C> void print(C const& c) { // works for Standard Containers, C-style arrays and legacy Containers std::copy(adl_begin(c), adl_end(c), std::ostream_iterator<decltype(*adl_begin(c))>(std::cout, " ")); std::cout << "/n"; // alternative: also works for Standard Containers, C-style arrays and legacy Containers // does not need adl_begin() / adl_end(), but continues to work for (auto elem: c) std::cout << elem << " "; std::cout << "/n"; }

Pros : convención de llamadas coherente que funciona de manera completamente genérica

  • las mismas ventajas que para la sugerencia de @ Xeo +
  • las repetidas declaraciones de uso han sido encapsuladas (DRY)

Contras : un poco detallado

  • adl_begin() / adl_end() no es tan escueto como begin() / end()
  • quizás tampoco sea tan idiomático (aunque es explícito)
  • la deducción pendiente del tipo de retorno C ++ 14 también contaminará el espacio de nombres con std::begin / std::end

NOTA : No estoy seguro de si esto realmente mejora con el enfoque anterior.

Calificación explícitamente std::begin() o std::end() todas partes

Una vez que la verbosidad de begin() / end() se ha abandonado de todos modos, ¿por qué no volver a las llamadas calificadas de std::begin() / std::end() ? Código de ejemplo:

// LegacyIntContainerBeginEnd.h namespace std { // retro-fitting begin() / end() interface on legacy IntContainer class // with incompatible names template<> auto begin(legacy::IntContainer& c) -> decltype(c.legacy_begin()) { return c.legacy_begin(); } // similary for begin() taking const&, cbegin(), end(), cend(), etc. } // namespace std // LegacyContainer.h namespace legacy { template<class T> class Container { public: // YES, DOCUMENT REALLY WELL THAT THE EXISTING CODE IS BEING MODIFIED auto begin() -> decltype(legacy_begin()) { return legacy_begin(); } auto end() -> decltype(legacy_end()) { return legacy_end(); } // rest of existing interface }; } // namespace legacy // print.h template<class C> void print(C const& c) { // works for Standard Containers, C-style arrays as well as // legacy::IntContainer and legacy::Container<T> std::copy(std::begin(c), std::end(c), std::ostream_iterator<decltype(*std::begin(c))>(std::cout, " ")); std::cout << "/n"; // alternative: also works for Standard Containers, C-style arrays and // legacy::IntContainer and legacy::Container<T> for (auto elem: c) std::cout << elem << " "; std::cout << "/n"; }

Pros : convención de llamadas coherente que funciona de forma casi genérica

  • funciona para cualquier contenedor estándar y tipos de usuario que definan miembros .begin() y .end()
  • funciona para arreglos de estilo C

Contras : un poco detallado y la adaptación no es genérica y un problema de mantenimiento

  • std::begin() / std::end() es un poco más detallado que begin() / end()
  • solo se puede adaptar para que funcione (¡también para bucles range-for !) para cualquier clase LegacyContainer que no tenga miembros .begin() y end() (y para los cuales no haya código fuente!) proporcionando especializaciones explícitas de los no plantillas de función de miembros begin() y end() en namespace std
  • solo se puede LegacyContainer<T> en las plantillas de clase LegacyContainer<T> añadiendo directamente las funciones de miembro begin() / end() dentro del código fuente de LegacyContainer<T> (que para las plantillas está disponible). El truco namespace std no funciona aquí porque las plantillas de funciones no pueden ser parcialmente especializadas.

Qué usar?

El enfoque ADL a través de non-member begin() / end() en el espacio de nombres propio de un contenedor es el enfoque idiomático C ++ 11, especialmente para funciones genéricas que requieren la actualización en clases heredadas y plantillas de clases. Es la misma expresión idiomática que para las funciones swap() non-member que proporcionan los usuarios.

Para el código que solo utiliza contenedores estándar o matrices de estilo C, std::begin() y std::end() se pueden llamar a todas partes sin introducir declaraciones de uso, a expensas de llamadas más detalladas. Este enfoque puede incluso actualizarse, pero requiere tocar el namespace std (para los tipos de clase) o las modificaciones de fuente en el lugar (para las plantillas de clase). Se puede hacer, pero no vale la pena el problema de mantenimiento.

En el código no genérico, donde el contenedor en cuestión es conocido en el momento de la codificación, uno podría incluso confiar en ADL para Contenedores Estándar solamente, y explícitamente calificar std::begin / std::end para arreglos de estilo C. Pierde cierta consistencia de llamada, pero ahorra en el uso de declaraciones.