c++ - algorithms - ¿Por qué siempre tengo que especificar explícitamente el rango en las funciones de algoritmo de STL, incluso si quiero trabajar en todo el contenedor?
libreria algorithm c++ (6)
Es fácil definir esa función usted mismo. Por ejemplo
#include <iostream>
#include <vector>
#include <algorithm>
#include <iterator>
template <class T>
decltype( auto ) min_element( T &c )
{
return std::min_element( std::begin( c ), std::end( c ) );
}
int main()
{
int a[] = { 5, 7, 3, 1, 9, 6 };
std::cout << *min_element( a ) << std::endl;
std::vector<int> v( std::begin( a ), std::end( a ) );
std::cout << *min_element( v ) << std::endl;
}
La salida del programa es
1
1
Hice una sugerencia para los algoritmos std::sort
y std::reverse
. Puedes leer sobre esto en mi foro personal que apoyo, como mi página de internet perosnal. here
Aunque está escrito en ruso, puedes traducirlo, por ejemplo, con Bing o Google.
Al usar las funciones de STL como sort()
o min_element()
, siempre tengo que especificar el rango por principio y fin explícitamente:
void range_example()
{
std::vector<int> list = {7, 3, 9, 1, 5, 2};
auto found_element = std::min_element(list.begin(), list.end());
std::cout << *found_element << std::endl;
}
Esto tiene sentido si pretendo trabajar solo en parte de mi contenedor, pero más a menudo necesito las funciones para trabajar en todo el contenedor. ¿Hay alguna razón por la que no haya una función sobrecargada que permita esto?
std::vector<int> list = {7, 3, 9, 1, 5, 2};
auto found_element = std::min_element(list);
¿Hay alguna manera de cumplir una llamada de función para el rango total de un contenedor que he pasado por alto?
EDITAR: Soy consciente de que puedo encapsular eso en una función, pero como esto debe hacerse para todas las funciones, me gustaría evitar eso si hay una mejor manera.
Esta es una de las veces en las que creo que está bien usar macros. Solo asegúrese de que calcular la expresión dentro de la macro no tenga efectos secundarios.
#include <boost/preprocessor/punctuation/comma.hpp>
// this is just: #define BOOST_PP_COMMA() ,
#define RANGE_ARGS( container ) container.begin ( ) BOOST_PP_COMMA() container.end ( )
#define RANGE_ARGS_C( container ) container.cbegin ( ) BOOST_PP_COMMA() container.cend ( )
#define RANGE_ARGS_R( container ) container.rbegin ( ) BOOST_PP_COMMA() container.rend ( )
#define RANGE_ARGS_CR( container ) container.crbegin ( ) BOOST_PP_COMMA() container.crend ( )
Esto cede, en tu caso:
std::vector<int> list = {7, 3, 9, 1, 5, 2};
auto const found_element = std::min_element( RANGE_ARGS_C(list) );
Esto se debe a que los algoritmos STL son independientes del contenedor. Los iteradores proporcionan una manera uniforme para que funcionen, con la única limitación de cuáles son las garantías que este algoritmo requiere de estos iteradores.
Por ejemplo, si desea realizar una búsqueda lineal para min_element()
, solo necesita iteradores hacia adelante (es decir, solo tienen que ser compatibles con el operator++
). Por lo tanto, puede escribir una implementación de plantilla simple que funcionará con prácticamente todos los contenedores, a pesar de cómo el contenedor se implementa bajo el capó.
Podría sobrecargar las funciones para tomar solo el contenedor y aplicar begin()
y end()
en ellas, pero esto significaría que tiene una interfaz más para recordar.
Editar
Supongo que hay algunos otros argumentos que podrían hacerse. Ya que STL era todo acerca de la belleza matemática y el énfasis en que los algoritmos están separados de los contenedores, los iteradores que pasan siempre refuerzan esta noción.
Por otro lado, en términos del lenguaje C ++ en su conjunto, uno de los objetivos principales de Stroustrup era educar a los desarrolladores. La potencia total de los algoritmos STL proviene de la capacidad de pasar rangos de iteradores arbitrarios, pero la mayoría de las veces desea operar en todo el contenedor. Si proporcionó sobrecargas para todo el contenedor, se podría argumentar que un gran número de personas nunca se molestaría en aprender a usar versiones de rango, porque serían precisamente esas versiones las que caerían en la categoría "otra interfaz para recordar".
La mayoría de las veces, la biblioteca estándar está diseñada para proporcionar la interfaz mínima necesaria para realizar todas las tareas requeridas, es decir, trata de evitar la expansión de la interfaz. Puede operar en un contenedor completo cuando el algoritmo acepta un par de iteradores, pero no puede operar en un subintervalo si el algoritmo acepta un contenedor. Entonces, el par de iteradores es más fundamental, y eso es lo que proporciona la biblioteca estándar. Las funciones de conveniencia generalmente no están incluidas.
Sin embargo, ciertamente no es la primera persona en pensar de esta manera, y existe toda la biblioteca Boost.Range dedicada a tratar un rango (tanto un contenedor como un rango arbitrario) como una entidad única en lugar de un par de iteradores.
También hay una propuesta formal para incorporar la biblioteca de rango de Eric Niebler en una versión futura de la biblioteca estándar de C ++.
La razón práctica por la que aún no se ha realizado la sobrecarga de contenedores o rangos tiene que ver con la propuesta de conceptos.
En este momento, los algoritmos toman una serie de parámetros de plantilla y les imponen requisitos. Si pasa tipos que no cumplen con los requisitos, pueden fallar en la compilación o simplemente no funcionar correctamente.
Las sobrecargas casi siempre implican un número diferente de parámetros.
Si agregáramos sobrecargas de contenedor / rango, tendríamos que darles nuevos nombres (ick) o modificar los algoritmos existentes para que sean inteligentes con la sobrecarga. Una sobrecarga (iterador, iterador, valor) y una sobrecarga (rango, valor, función) tienen el mismo número de argumentos, y a cuál se llama, podría confundirse fácilmente con el compilador (y podrían ocurrir resultados inesperados).
Si bien podríamos ir y especificar restricciones de sobrecarga en todos los algoritmos existentes uno por uno, luego agregar las sobrecargas para los rangos, en este punto el código y los requisitos serían desagradables. Después de agregar los conceptos al lenguaje, esperamos que ambos tengamos un conjunto de conceptos concisos que describan cuáles deberían ser los parámetros, y una característica del lenguaje que hace que la implementación sea fácil y limpia.
Puede resultar que estos algoritmos no sean prácticamente sobrecargas de los algoritmos existentes, debido a razones de compatibilidad o lo que tenga, pero incluso esto será más fácil de resolver.
Originalmente, los iteradores eran suficientes y separan los contenedores de los algoritmos. Se podrían haber agregado rangos en ese momento, pero la maquinaria lingüística para la interpretación de contenedores en un rango limpio carecía de algo (por ejemplo, el tipo de letra es útil), y no era estrictamente necesario. Desde entonces, se ha deseado el soporte de rango, pero no es fácil hacerlo de manera limpia, y hay (en el horizonte) una extensión de lenguaje que lo hará mucho más limpio y fácil.
Usted podría implementar su propio:
template<class Container>
typename Container::iterator min_element(Container& c) {
using std::begin;
using std::end;
return std::min_element(begin(c), end(c));
}
std::vector<int> list = {7, 3, 9, 1, 5, 2};
auto found_element = min_element(list);
Por completitud:
template<class Container>
typename std::conditional<
std::is_const<Container>::value,
typename Container::const_iterator,
typename Container::iterator>::type min_element(Container& c) {
using std::begin;
using std::end;
return std::min_element(begin(c), end(c));
}
y para soportar matrices:
template<typename T, size_t N>
T* min_element(T (&arr)[N]) { return std::min_element(arr, arr + N); }