c++ - geeksforgeeks - Pase un solo parámetro a una función que espera un rango de iterador
list iterator c++ (4)
Creo que lo haría en dos pasos:
Defina una sobrecarga de la función de plantilla que toma un contenedor, escrita en términos de la versión del iterador.
Defina una clase de proxy que trate una referencia de objeto como una matriz de tamaño 1.
Ejemplo de c ++ 17:
#include <iterator>
#include <type_traits>
#include <vector>
#include <iostream>
// proxy object
template<class T>
struct object_as_container
{
using value_type = T;
using iterator = T*;
using const_iterator = std::add_const_t<T>;
object_as_container(value_type& val) : object_(val) {}
const_iterator begin() const { return std::addressof(object_); }
iterator begin() { return std::addressof(object_); }
const_iterator end() const { return std::next(begin()); }
iterator end() { return std::next(begin()); }
private:
value_type& object_;
};
// our function in terms of iterators
template<class Iter> void func(Iter first, Iter last)
{
while(first != last)
{
std::cout << *first++;
}
}
// our function in terms of containers
template<class Container> void func(Container&& cont)
{
func(cont.begin(), cont.end());
}
int main()
{
const int value = 5;
func(object_as_container(value));
func(std::vector { 1,2,3,4,5 });
}
Considere una función que acepte uno o más parámetros (por ejemplo, nombres de archivos). Para que sea versátil, es conveniente escribirlo para una gama de iteradores generales:
template<class Iter>
void function(Iter first, Iter last)
{
// do something
}
Ahora podemos invocarlo de la siguiente manera, independientemente de cómo almacenemos los argumentos:
WhateverContainer container;
function(std::begin(container), std::end(container));
Por ejemplo, el STL se basa en gran medida en este paradigma.
Ahora, imagine que queremos invocar la función con un único argumento que no está almacenado en un contenedor. Por supuesto que podemos escribir:
const int value = 5;
std::vector<int> vec(1, value);
function(std::begin(vec), std::end(vec));
Pero esta solución me parece torpe y derrochadora.
Pregunta: ¿Existe una mejor manera de crear una representación de una sola variable compatible con el rango de iteradores?
Puede tratarlo como una matriz de un elemento por [expr.unary.op]/3 :
function(&value, &value + 1);
Para los fines de la aritmética de punteros ([expr.add]) y la comparación ([expr.rel], [expr.eq]), se considera que un objeto que no es un elemento de matriz cuya dirección se toma de esta manera pertenece a una matriz con un elemento de tipo T.
Puedes usar punteros, por una vez:
function(&value, &value + 1);
En código genérico, std::addressof
lugar del operador std::addressof
&
es algo más seguro, dependiendo de su nivel de paranoia.
Por supuesto, puede envolver esto en una sobrecarga para un uso más sencillo:
template <class T>
decltype(auto) function (T &&e) {
auto p = std::addressof(e);
return function(p, p + 1);
}
También puede sobrecargar su función de plantilla de function
para un rango de un solo elemento :
template<typename Iter>
void function(Iter first) {
return function(first, std::next(first)); // calls your original function
}
De esta manera, su función de function
original sigue siendo compatible con los rangos de iteradores. Sin embargo, tenga en cuenta que el uso de esta sobrecarga con un rango vacío resultará en un comportamiento indefinido.
Para un solo elemento, value
, puede usar la sobrecarga arriba:
function(&value); // calls overload
Como el operador &
puede estar sobrecargado, considere también usar std::addressof
lugar de &
, como ya se mencionó en esta respuesta .
Para un rango que consta de un solo elemento, también puede usar la sobrecarga anterior, que solo necesita un iterador en lugar de un par de iteradores:
const int value = 5;
std::vector<int> vec(1, value); // single-element collection
function(std::begin(vec)); // <-- calls overload