Est-il possible en C++ de faire l'itération std::map "for element: container" avec des variables nommées (par exemple, clé et valeur) au lieu de.premier et.deuxième?

Je ne savais pas quoi chercher. J'ai trouvé renommant le premier et le deuxième itérateur de carte mais ce n'est pas tout à fait ce que je veux faire.

voici ce que j'aimerais faire [voir ci-dessous pour le code C++ absurde]. Est quelque chose de proche de ce possible? Sinon, juste pour aller avec l'option de "s'adapter" l'itérateur comme la première ligne à l'intérieur de la boucle, je suppose.

// 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 très bien, mais plutôt éviter boost si possible.

le plus proche que j'ai obtenu est

// 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;
}

mais cela signifie écrire une classe d'adaptateur pour chaque carte: (

// slightly joke answer/"what could possibly go wrong?"
#define key first
#define value second
26
demandé sur Community 2016-04-07 18:46:21

7 réponses

une approche inspirée de Barry ci-dessous serait d'écrire un adaptateur d'autonomie.

Faire sans boost ou similaire support de bibliothèque est une douleur, mais:

  1. écrivez un modèle de gamme. Il stocke 2 class iterator s et a begin() et end() méthodes (et tout ce que vous voulez).

  2. écrivez un adaptateur d'itérateur transformant. Il faut un itérateur, et l'emballe de sorte que son type de valeur est transformé par un objet de fonction F.

  3. écrivez un to_kv transformateur qui prend un std::pair<K, V> cv& et retourne un struct kv_t { K cv& key; V cv& value; } .

  4. fil 3 en 2 en 1 et appelez-le as_kv . Il faut une gamme de paires, et renvoie une gamme de clés-valeurs.

la syntaxe que vous obtenez est:

std::map<int, std::string> m;

for (auto kv : as_kv(m)) {
  std::cout << kv.key << "->" << kv.value << "\n";
}

ce qui est bien.

Voici une solution minimaliste qui ne crée pas réellement des itérateurs juridiques, mais soutient 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"}}; }

qui est très minime, mais fonctionne. Je n'encouragerais généralement pas ce genre de pseudo itérateurs à moitié-assed pseudo pour for(:) boucles; l'utilisation de vrais itérateurs est seulement un coût supplémentaire modeste, et ne surprend pas les gens plus tard.

live exemple

(maintenant avec support temporaire map. Ne supporte pas les tableaux c plats ... pourtant)

le range-trick stocke un conteneur (éventuellement une référence) afin de copier des conteneurs temporaires dans l'objet stocké pour la durée de la boucle for(:) . Contenants Non temporaires le type Container est une sorte de Foo& , donc il ne fait pas de copie redondante.

par contre, kv_t ne contient évidemment que des références. Il pourrait y avoir un cas étrange d'itérateurs retournant des temporairesqui cassent cette implémentation kv_t , mais je ne sais pas comment l'éviter en général sans sacrifier la performance dans les cas plus courants.


si vous n'aimez pas la partie kv. de ce qui précède, nous pouvons faire quelques solutions, mais elles ne sont pas aussi propres.

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)}; }

puis:

map_for(m)->*[&](auto key, auto& value) {
  std::cout << key << (value += " ") << '\n';
};

assez proche?

live exemple

il y a quelques propositions autour des tuples de première classe (et donc des paires) qui peuvent vous donner quelque chose comme ça, mais je ne sais pas le statut des propositions.

la syntaxe que vous pourriez avoir si cela entrait en C++ ressemblerait à quelque chose comme ceci:

for( auto&& [key, value] : container )

Commentaires sur le ->* l'abomination ci-dessus:

ainsi le ->* est utilisé comme une sorte de operator bind de Haskell (ainsi que le déballage de tuple implicite), et nous l'alimentons un lambda qui prend les données contenues dans la carte et retourne vide. Le type de retour (Haskell-esque) devient une map over void (nothing), que j'élide dans void.

la technique a un problème: vous perdez break; et continue; qui sucent.

une variante moins hackey inspirée de Haskell s'attendrait à ce que la lambda retourne quelque chose comme void | std::experimental::expected<break_t|continue_t, T> , et si T est void ne rien retourner, si T est un tuple-type retourner une carte, et si T est une carte joindre la carte-type retourné. Il pourrait également déballer ou ne pas déballer le tuple contenu en fonction de ce que veut la lambda (SFINAE-style detection).

mais c'est un peu trop pour un SO réponse; cette digression souligne que le style de programmation n'est pas complète impasse. Il n'est cependant pas conventionnel en C++.

17
répondu Yakk - Adam Nevraumont 2018-06-10 00:31:30

vous pouvez écrire un modèle de classe:

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)
    { }
};

avec l'avantage d'être en mesure d'écrire key et value , mais avec l'inconvénient d'avoir à spécifier les types:

for ( MapElem<int, std::string> kv : my_map ){
    std::cout << kv.key << " --> " << kv.value;
}

et ça ne marchera pas si my_map était const non plus. Il faudrait faire quelque chose comme:

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;
}

c'est le bordel. La meilleure chose pour le moment est de s'habituer à écrire .first et .second et espérer que la proposition de reliures structurées passe, ce qui permettrait ce que vous voulez vraiment:

for (auto&& [key, value] : my_map) {
    std::cout << key << " --> " << value;
}
6
répondu Barry 2016-04-07 17:08:13

la chose la plus proche de l'utiliser 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;
}

bien sûr, les expressions ne peuvent pas être placées dans la boucle for, donc une macro pourrait être utilisée pour permettre l'expression:

#define FOREACH(var, cont) \
    for(auto && _p:cont) \
        if(bool _done = false) {} \
        else for(var = std::forward<decltype(_p)>(_p); !_done; _done = true)

alors std::tie peut être utilisé directement dans la boucle:

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;
}
3
répondu Paul Fultz II 2016-04-07 18:15:02

Avec c++moderne 17 c'est désormais possible avec structuré liaisons .

#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;
}

Construire:

$ clang++ -std=c++17 test.cpp -o program

sortie:

$ ./program
0,hello
1,world
3
répondu Cusiman7 2018-06-09 23:46:03

juste pour fournir encore une autre façon de presque faites ce que vous voulez, j'ai écrit cela il y a quelque temps pour éviter d'avoir .first et .second sur tout mon code:

auto pair2params = [](auto&& f)
{
    return [f](auto&& p) {
        f(p.first, p.second);
    };
};

Maintenant vous pouvez écrire quelque chose comme (en supposant une gamme basée 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";
    });
}

exemple D'exécution: http://ideone.com/Bs9Ctm

1
répondu Bret Kuhns 2016-04-11 15:06:12

en général, je préfère un BAISER pour cette approche:

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;

avec un exemple d'utilisation comme ceci:

for(auto& kvp : my_map) {
    std::cout << key(kvp) << " " << value(kvp) << "\n";
}
1
répondu milleniumbug 2016-04-12 17:58:40

dans Apache Mesos , nous utilisons une macro appelée foreachpair qui peut être utilisé comme ceci:

foreachpair (const Key& key, const Value& value, elems) {
  /* ... */
}

vous pouvez bien sûr remplacer Key et Value par auto , avec tous les qualificatifs que vous souhaitez y utiliser. Il supporte également break et continue .

ma dernière implémentation ressemble à ceci:

#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__)

les macros ci-dessus fournissent unique les noms de divers composants qui sont utilisés dans la macro foreachpair en incluant le nombre __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)

je traverserai cette ligne par la ligne.

  1. (début du mess).
  2. de la Gamme, à base de boucle qui itère ELEMS .
  3. nous avons mis en place le étiquette FOREACH_BREAK . Nous sautons à cette étiquette à break hors de cette boucle.
  4. nous avons mis en place le indicateur de flux de contrôle FOREACH_CONTINUE . Il s'agit de true si l'itération courante est sortie normalement, ou via continue , et false si l'itération courante est sortie via break .
  5. nous sautons toujours à l'étiquette FOREACH_BODY , ci-dessous.
  6. c'est ici que nous interceptons le flux de contrôle et que nous inspectons le drapeau FOREACH_CONTINUE pour déterminer comment nous avons quitté l'itération en cours.
  7. si FOREACH_CONTINUE est false , nous savons que nous sommes sortis par break donc nous sautons à FOREACH_BREAK .
  8. autrement, FOREACH_CONTINUE est true , et nous break de la boucle for (;;) qui nous amène à la prochaine itération.
  9. (mi-chemin à travers le mess).
  10. Nous avons toujours sauter ici, à partir de (5).
  11. configurer FOREACH_ONCE qui est juste utilisé pour exécuter le for boucle qui déclare KEY exactement une fois.
  12. Déclarer KEY .
  13. avancer l'élément correctement.
  14. utilisez FOREACH_ONCE pour vous assurer que cette boucle est exécutée exactement une fois.
  15. Déclarer VALUE .
  16. avancer l'élément correctement.
  17. Utiliser FOREACH_CONTINUE pour s'assurer que cette boucle est exécutée exactement une fois, et pour indiquer si la boucle sortie via break ou non.

NOTE : l'utilisation de std::get permet le support de std::tuple ou std::array sortant également de la séquence. par exemple, std::vector<std::tuple<int, int>>

Ideone Démo

1
répondu mpark 2016-04-14 06:28:54