¿Es posible en C++ realizar la iteración std:: map<> “para elemento: contenedor” con variables nombradas(por ejemplo, clave y valor) en lugar de.first y.second?
c++11 dictionary (7)
No estaba seguro de qué buscar. Encontré Renombrar primero y segundo de un iterador de mapa, pero no es exactamente lo que quiero hacer.
Esto es lo que me gustaría hacer [vea a continuación el código C ++ sin sentido]. ¿Es algo cercano a esto posible? De lo contrario, solo tendré que ir con la opción de "adaptar" el iterador como la primera línea dentro del bucle, supongo.
// what I want to do:
std::map<int, std::string> my_map;
// ... populate my_map
for(auto key, auto & value: my_map){
// do something with integer key and string value
}
C ++ 11 está bien, pero evita el refuerzo si es posible.
Lo más cerca que he estado es
// TODO, can this be templated?
struct KeyVal{
int & id;
std::string & info;
template <typename P>
KeyVal(P & p)
: id(p.first)
, info(p.second)
{
}
};
//...
for ( KeyVal kv : my_map ){
std::cout << kv.info;
}
Pero eso significa escribir una clase de adaptador para cada mapa :(
// slightly joke answer/"what could possibly go wrong?"
#define key first
#define value second
Con c ++ 17 moderno, esto es ahora posible con enlaces estructurados .
#include <map>
#include <string>
#include <iostream>
using namespace std;
int main() {
map<int, string> my_map;
my_map[0] = "hello";
my_map[1] = "world";
for (auto&& [key, value] : my_map) {
cout << key << "," << value << "/n";
}
return 0;
}
Constrúyelo:
$ clang++ -std=c++17 test.cpp -o program
Salida:
$ ./program
0,hello
1,world
En Apache Mesos , usamos una macro llamada foreachpair
que se puede usar así:
foreachpair (const Key& key, const Value& value, elems) {
/* ... */
}
Por supuesto, puede reemplazar la Key
y el Value
con auto
, junto con los calificadores que quiera usar allí. También es compatible con break
y continue
.
Mi última implementación se ve así:
#define FOREACH_PREFIX BOOST_PP_CAT(foreach_, __LINE__)
#define FOREACH_BODY BOOST_PP_CAT(FOREACH_PREFIX, _body__)
#define FOREACH_BREAK BOOST_PP_CAT(FOREACH_PREFIX, _break__)
#define FOREACH_CONTINUE BOOST_PP_CAT(FOREACH_PREFIX, _continue__)
#define FOREACH_ELEM BOOST_PP_CAT(FOREACH_PREFIX, _elem__)
#define FOREACH_ONCE BOOST_PP_CAT(FOREACH_PREFIX, _once__)
Las macros anteriores proporcionan nombres únicos a varios componentes que se utilizan en la macro foreachpair
al incluir el número __LINE__
.
1 #define foreachpair(KEY, VALUE, ELEMS) /
2 for (auto&& FOREACH_ELEM : ELEMS) /
3 if (false) FOREACH_BREAK: break; /* set up the break path */ /
4 else if (bool FOREACH_CONTINUE = false) {} /* var decl */ /
5 else if (true) goto FOREACH_BODY; /* skip the loop exit checks */ /
6 else for (;;) /* determine whether we should break or continue. */ /
7 if (!FOREACH_CONTINUE) goto FOREACH_BREAK; /* break */ /
8 else if (true) break; /* continue */ /
9 else /
10 FOREACH_BODY: /
11 if (bool FOREACH_ONCE = false) {} /* var decl */ /
12 else for (KEY = std::get<0>( /
13 std::forward<decltype(FOREACH_ELEM)>(FOREACH_ELEM)); /
14 !FOREACH_ONCE; FOREACH_ONCE = true) /
15 for (VALUE = std::get<1>( /
16 std::forward<decltype(FOREACH_ELEM)>(FOREACH_ELEM)); /
17 !FOREACH_CONTINUE; FOREACH_CONTINUE = true)
Voy a caminar a través de esta línea por línea.
- (Inicio del lío).
- Rango de bucle for basado en
ELEMS
sobreELEMS
. - Creamos la etiqueta
FOREACH_BREAK
. Saltamos a esta etiqueta para salir de este bucle. - Configuramos el indicador de flujo de control
FOREACH_CONTINUE
. Esto estrue
si la iteración actual se cerró normalmente, o por medio decontinue
, yfalse
si la iteración actual se cerró porbreak
. - Siempre
FOREACH_BODY
a la etiquetaFOREACH_BODY
, a continuación. - Aquí es donde interceptamos el flujo de control e inspeccionamos el indicador
FOREACH_CONTINUE
para determinar cómo salimos de la iteración actual. - Si
FOREACH_CONTINUE
esfalse
, sabemos que salimos a través de labreak
así queFOREACH_BREAK
aFOREACH_BREAK
. - De lo contrario,
FOREACH_CONTINUE
estrue
y salimos del buclefor (;;)
que nos lleva a la siguiente iteración. - (A mitad del camino).
- Siempre saltamos aquí desde (5).
- Configure
FOREACH_ONCE
que solo se utiliza para ejecutar el buclefor
que declaraKEY
exactamente una vez. - Declarar
KEY
. - Adelante el elemento correctamente.
- Use
FOREACH_ONCE
para asegurarse de que este bucle se ejecute exactamente una vez. - Declarar
VALUE
. - Adelante el elemento correctamente.
- Use
FOREACH_CONTINUE
para asegurarse de que este bucle se ejecute exactamente una vez, y para indicar si el bucle se salió porbreak
o no.
NOTA : El uso de std::get
permite el soporte de std::tuple
o std::array
que sale de la secuencia también. por ejemplo, std::vector<std::tuple<int, int>>
Lo más cercano a usar std::tie
:
std::map<int, std::string> my_map;
int key;
std::string value;
for(auto&& p: my_map)
{
std::tie(key, value) = p;
std::cout << key << ": " << value << std::endl;
}
Por supuesto, las expresiones no se pueden colocar en el bucle for range, por lo que, en lugar de eso, se podría usar una macro para permitir la expresión:
#define FOREACH(var, cont) /
for(auto && _p:cont) /
if(bool _done = false) {} /
else for(var = std::forward<decltype(_p)>(_p); !_done; _done = true)
Entonces, std::tie
puede usarse directamente en el bucle:
std::map<int, std::string> my_map;
int key;
std::string value;
FOREACH(std::tie(key, value), my_map)
{
std::cout << key << ": " << value << std::endl;
}
Normalmente prefiero un enfoque KISS para esto:
template<typename KeyValuePair>
typename KeyValuePair::first_type& key(KeyValuePair& kvp)
{
return kvp.first;
}
template<typename KeyValuePair>
const typename KeyValuePair::first_type& key(const KeyValuePair& kvp)
{
return kvp.first;
}
template<typename KeyValuePair>
void key(const KeyValuePair&& kvp) = delete;
template<typename KeyValuePair>
typename KeyValuePair::second_type& value(KeyValuePair& kvp)
{
return kvp.second;
}
template<typename KeyValuePair>
const typename KeyValuePair::second_type& value(const KeyValuePair& kvp)
{
return kvp.second;
}
template<typename KeyValuePair>
void value(const KeyValuePair&& kvp) = delete;
con ejemplo de uso como este:
for(auto& kvp : my_map) {
std::cout << key(kvp) << " " << value(kvp) << "/n";
}
Podrías escribir una plantilla de clase:
template <class K, class T>
struct MapElem {
K const& key;
T& value;
MapElem(std::pair<K const, T>& pair)
: key(pair.first)
, value(pair.second)
{ }
};
con la ventaja de poder escribir key
y value
pero con la desventaja de tener que especificar los tipos:
for ( MapElem<int, std::string> kv : my_map ){
std::cout << kv.key << " --> " << kv.value;
}
Y eso tampoco funcionará si my_map
fuera const
. Tendrías que hacer algo como:
template <class K, class T>
struct MapElem {
K const& key;
T& value;
MapElem(std::pair<K const, T>& pair)
: key(pair.first)
, value(pair.second)
{ }
MapElem(const std::pair<K const, std::remove_const_t<T>>& pair)
: key(pair.first)
, value(pair.second)
{ }
};
for ( MapElem<int, const std::string> kv : my_map ){
std::cout << kv.key << " --> " << kv.value;
}
Es un desastre. Lo mejor por ahora es simplemente acostumbrarse a escribir .second
y .second
y esperar que se .second
la propuesta de enlaces estructurados, lo que permitiría lo que realmente desea:
for (auto&& [key, value] : my_map) {
std::cout << key << " --> " << value;
}
Solo por proporcionar otra forma de hacer casi lo que quieres, escribí esto hace un tiempo para evitar tener .second
y .second
todo mi código:
auto pair2params = [](auto&& f)
{
return [f](auto&& p) {
f(p.first, p.second);
};
};
Ahora puedes escribir algo como (asumiendo un rango basado en for_each
):
int main()
{
auto values = map<int, string>{
{0, "hello"},
{1, "world!"}
};
for_each(values, pair2params([](int key, const string& value) {
cout << key << ": " << value << "/n";
});
}
Ejemplo de ejecución: http://ideone.com/Bs9Ctm
Un enfoque inspirado por Barry a continuación sería escribir un adaptador de rango.
Hacer esto sin boost
o un soporte de biblioteca similar es un dolor, pero:
Escribe una plantilla de rango. Almacena 2
class iterator
y tiene los métodosbegin()
yend()
(y lo que quieras).Escribe un adaptador iterador de transformación. Toma un iterador y lo envuelve para que su tipo de valor sea transformado por algún objeto de función F.
Escriba un transformador
to_kv
que tome unstd::pair<K, V> cv&
y devuelva unastruct kv_t { K cv& key; V cv& value; }
struct kv_t { K cv& key; V cv& value; }
struct kv_t { K cv& key; V cv& value; }
.Conecta 3 en 2 en 1 y llámalo
as_kv
. Toma un rango de pares y devuelve un rango de valores-clave.
La sintaxis con la que terminas es:
std::map<int, std::string> m;
for (auto kv : as_kv(m)) {
std::cout << kv.key << "->" << kv.value << "/n";
}
lo cual es bueno.
Aquí hay una solución minimalista que en realidad no crea iteradores legales, pero admite for(:)
:
template<class Key, class Value>
struct kv_t {
Key&& key;
Value&& value;
};
// not a true iterator, but good enough for for(:)
template<class Key, class Value, class It>
struct kv_adapter {
It it;
void operator++(){ ++it; }
kv_t<Key const, Value> operator*() {
return {it->first, it->second};
}
friend bool operator!=(kv_adapter const& lhs, kv_adapter const& rhs) {
return lhs.it != rhs.it;
}
};
template<class It, class Container>
struct range_trick_t {
Container container;
range_trick_t(Container&&c):
container(std::forward<Container>(c))
{}
It begin() { return {container.begin()}; }
It end() { return {container.end()}; }
};
template<class Map>
auto as_kv( Map&& m ) {
using std::begin;
using iterator = decltype(begin(m)); // no extra (())s
using key_type = decltype((begin(m)->first)); // extra (())s on purpose
using mapped_type = decltype((begin(m)->second)); // extra (())s on purpose
using R=range_trick_t<
kv_adapter<key_type, mapped_type, iterator>,
Map
>;
return R{std::forward<Map>(m)};
}
std::map<int, std::string> m() { return {{0, "Hello"}, {2, "World"}}; }
Lo cual es muy mínimo, pero funciona. Por lo general, no recomendaría este tipo de pseudo iteradores a medias para for(:)
loops; usar iteradores reales es solo un costo adicional modesto, y no sorprende a las personas más adelante.
(Ahora con soporte de mapas temporal. No es compatible con matrices C planas ... todavía)
Range-trick almacena un contenedor (posiblemente una referencia) para copiar contenedores temporales en el objeto almacenado durante el ciclo for(:)
. Container
no temporales el tipo de Container
es un Foo&
de algún tipo, por lo que no hace una copia redundante.
Por otro lado, kv_t
obviamente solo almacena referencias. Podría haber un caso extraño de iteradores que devuelven temporarios que rompen esta implementación de kv_t
, pero no estoy seguro de cómo evitarlo en general sin sacrificar el rendimiento en casos más comunes.
Si no te gusta el kv.
parte de lo anterior, podemos hacer algunas soluciones, pero no son tan limpias.
template<class Map>
struct for_map_t {
Map&& loop;
template<class F>
void operator->*(F&& f)&&{
for (auto&& x:loop) {
f( decltype(x)(x).first, decltype(x)(x).second );
}
}
};
template<class Map>
for_map_t<Map> map_for( Map&& map ) { return {std::forward<Map>(map)}; }
entonces:
map_for(m)->*[&](auto key, auto& value) {
std::cout << key << (value += " ") << ''/n'';
};
¿suficientemente cerca?
Hay algunas propuestas en torno a las tuplas de primera clase (y, por lo tanto, pares) que pueden ofrecerle algo así, pero no conozco el estado de las propuestas.
La sintaxis que puede terminar si esto llega a C ++ sería algo como esto:
for( auto&& [key, value] : container )
Comentarios sobre la abominación ->*
arriba:
Por lo tanto, el ->*
se está utilizando como un operator bind
de Haskell (junto con el desempaquetado implícito de la tupla), y le estamos suministrando un lambda que toma los datos contenidos en el mapa y devuelve el vacío. El tipo de retorno (Haskell-esque) se convierte en un mapa sobre el vacío (nada), que elijo en vacío.
La técnica tiene un problema: se pierde break;
y continue;
que chupan.
Una variante menos hackey inspirada en Haskell esperaría que la lambda devuelva algo así como void | std::experimental::expected<break_t|continue_t, T>
void | std::experimental::expected<break_t|continue_t, T>
, y si T
es void
, no devuelve nada, si T
es un tipo de tupla devuelve un mapa, y si T
es un mapa se une al tipo de mapa devuelto. También desempaquetaría o no desempaquetaría la tupla contenida según lo que quiera la lambda (detección al estilo SFINAE).
Pero eso es demasiado para una respuesta SO; esta digresión señala que el estilo de programación anterior no es un callejón sin salida completo. Sin embargo, es poco convencional en C ++.