Est-il possible de déclarer deux variables de types différents dans une boucle for?

240

Est-il possible de déclarer deux variables de types différents dans le corps d'initialisation d'une boucle for en C ++?

Par exemple:

for(int i=0,j=0 ...

définit deux entiers. Puis-je définir un intet un chardans le corps d'initialisation? Comment cela se ferait-il?

Nathan Osman
la source
3
Il est possible dans g ++ - 4.4 ( -std=c++0x) sous la forme de for(auto i=0, j=0.0; ..., mais cette possibilité a été supprimée dans g ++ - 4.5 pour coïncider avec les textes c ++ 0x.
rafak

Réponses:

133

C ++ 17 : Oui! Vous devez utiliser une déclaration contraignante structurée . La syntaxe est prise en charge dans gcc et clang depuis des années (depuis gcc-7 et clang-4.0) ( exemple live clang ). Cela nous permet de décompresser un tuple comme ceci:

for (auto [i, f, s] = std::tuple{1, 1.0, std::string{"ab"}}; i < N; ++i, f += 1.5) {
    // ...
}

Ce qui précède vous donnera:

  • int i mis à 1
  • double f mis à 1.0
  • std::string s mis à "ab"

Assurez-vous de #include <tuple>ce type de déclaration.

Vous pouvez spécifier les types exacts à l'intérieur du tupleen les tapant tous comme je l'ai fait avec le std::string, si vous voulez nommer un type. Par exemple:

auto [vec, i32] = std::tuple{std::vector<int>{3, 4, 5}, std::int32_t{12}}

Une application spécifique de cela est l'itération sur une carte, l'obtention de la clé et de la valeur,

std::unordered_map<K, V> m = { /*...*/ };
for (auto& [key, value] : m) {
   // ...
}

Voir un exemple en direct ici


C ++ 14 : vous pouvez faire la même chose que C ++ 11 (ci-dessous) avec l'ajout de type std::get. Donc, au lieu de std::get<0>(t)dans l'exemple ci-dessous, vous pouvez avoir std::get<int>(t).


C ++ 11 : std::make_pairvous permet de le faire, ainsi que std::make_tuplepour plus de deux objets.

for (auto p = std::make_pair(5, std::string("Hello World")); p.first < 10; ++p.first) {
    std::cout << p.second << std::endl;
}

std::make_pairrenverra les deux arguments dans un std::pair. Les éléments sont accessibles avec .firstet .second.

Pour plus de deux objets, vous devrez utiliser un std::tuple

for (auto t = std::make_tuple(0, std::string("Hello world"), std::vector<int>{});
        std::get<0>(t) < 10;
        ++std::get<0>(t)) {
    std::cout << std::get<1>(t) << std::endl; // cout Hello world
    std::get<2>(t).push_back(std::get<0>(t)); // add counter value to the vector
}

std::make_tupleest un modèle variadique qui va construire un tuple de n'importe quel nombre d'arguments (avec quelques limitations techniques bien sûr). Les éléments sont accessibles par index avecstd::get<INDEX>(tuple_object)

Dans les corps de boucle for, vous pouvez facilement aliaser les objets, même si vous devez toujours utiliser .firstou std::getpour la condition de boucle for et l'expression de mise à jour

for (auto t = std::make_tuple(0, std::string("Hello world"), std::vector<int>{});
        std::get<0>(t) < 10;
        ++std::get<0>(t)) {
    auto& i = std::get<0>(t);
    auto& s = std::get<1>(t);
    auto& v = std::get<2>(t);
    std::cout << s << std::endl; // cout Hello world
    v.push_back(i); // add counter value to the vector
}

C ++ 98 et C ++ 03 Vous pouvez nommer explicitement les types de a std::pair. Il n'y a pas de moyen standard de généraliser cela à plus de deux types:

for (std::pair<int, std::string> p(5, "Hello World"); p.first < 10; ++p.first) {
    std::cout << p.second << std::endl;
}
Ryan Haining
la source
5
Si vous faites du C ++ 17, vous pouvez même supprimer le make_et écrire std::pair(1, 1.0).
Marc Glisse
Les affaires de tuple / paire poilues de style C ++ 14 - tout va bien (probablement, vote positif), mais ça a l'air bizarre :)
mlvljr
3
En bref: oui c'est possible, mais ça ne va pas être joli.
Un programmeur du
Ouais pas joli, mais c'est super! J'ai vraiment apprécié le tuple-ish. :) Mais c'est vraiment une qualité syntaxique très peu intuitive des boucles for en C ++ et cela m'a donné des maux de tête pendant plus d'une demi-heure pour enfin réaliser ce qui devait être googlé ...
aderchox
@aderchox si vous pouvez clarifier votre malentendu, je peux mettre à jour la réponse
Ryan Haining
276

Non - mais techniquement, il existe une solution de contournement (pas que je l'utilise en fait à moins d'y être forcé):

for(struct { int a; char b; } s = { 0, 'a' } ; s.a < 5 ; ++s.a) 
{
    std::cout << s.a << " " << s.b << std::endl;
}
Georg Fritzsche
la source
3
Cela ne compile pas sur VS 2008, mais sur Comeau en ligne ;-)
JRL
7
@JRL: Oh, VS2005 non plus. Encore une autre fonctionnalité de non-conformité dans VC ++, je suppose.
Georg Fritzsche
3
J'ai fait l'équivalent en Perl. Cependant, je n'ai pas essayé de dissimuler quelque chose comme ça à travers une revue de code en C ++.
John
21
avec c ++ 11 I, vous pouvez raccourcir cet exemple en utilisant des valeurs par défautstruct { int a=0; char b='a'; } s;
Ryan Haining
1
Cette réponse répond aux exigences de la réponse, mais à partir d'un PDV de lisibilité, je préfère @MK. est la réponse. La solution de MK répond même à la portée en ajoutant des accolades.
Trevor Boyd Smith
221

Pas possible, mais vous pouvez faire:

float f;
int i;
for (i = 0,f = 0.0; i < 5; i++)
{
  //...
}

Ou, limitez explicitement la portée fet l' iutilisation de supports supplémentaires:

{
    float f; 
    int i;
    for (i = 0,f = 0.0; i < 5; i++)
    {
       //...
    }
}
MK.
la source
Je sais que c'est une très vieille question, mais pouvez-vous expliquer pourquoi certains le feraient avec les crochets supplémentaires autour, comme dans votre deuxième exemple?
ford
13
@fizzisist pour limiter explicitement la portée de f et i aux seules parties du code où elles sont utilisées.
MK.
1
@MK. Merci, c'est ce que je soupçonnais. J'ai modifié votre réponse pour l'expliquer.
ford
Une seule question: pourquoi comme ça? : O
rohan-patel
Parce que cela fonctionne comme «int a = 0, b = 4», je suppose. Cela étant dit, la portée de f et i ne sera probablement utile que pour empêcher la réutilisation de ces noms (ce qui est une bonne raison), mais le code généré sera généralement le même sur un compilateur moderne (dans ce cas).
Asu
14

Vous ne pouvez pas déclarer plusieurs types dans l'initialisation, mais vous pouvez attribuer à plusieurs types EG

{
   int i;
   char x;
   for(i = 0, x = 'p'; ...){
      ...
   }
}

Déclarez-les simplement dans leur propre champ d'application.

zmbush
la source
3

Je pense que la meilleure approche est la réponse de xian .

mais...


# Imbriqué pour la boucle

Cette approche est sale, mais peut résoudre toutes les versions.

donc je l'utilise souvent dans les fonctions macro.

for(int _int=0, /* make local variable */ \
    loopOnce=true; loopOnce==true; loopOnce=false)

    for(char _char=0; _char<3; _char++)
    {
        // do anything with
        // _int, _char
    }

Supplémentaire 1.

Il peut également être utilisé pour declare local variableset initialize global variables.

float globalFloat;

for(int localInt=0, /* decalre local variable */ \
    _=1;_;_=0)

    for(globalFloat=2.f; localInt<3; localInt++) /* initialize global variable */
    {
        // do.
    }

Supplémentaire 2.

Bon exemple: avec fonction macro.

(Si la meilleure approche ne peut pas être utilisée car il s'agit d'une macro for-loop)

#define for_two_decl(_decl_1, _decl_2, cond, incr) \
for(_decl_1, _=1;_;_=0)\
    for(_decl_2; (cond); (incr))


    for_two_decl(int i=0, char c=0, i<3, i++)
    {
        // your body with
        // i, c
    }

# Astuce if-statement

if (A* a=nullptr);
else
    for(...) // a is visible

Si vous souhaitez initialiser à 0ou nullptr, vous pouvez utiliser cette astuce.

mais je ne le recommande pas à cause d'une lecture difficile.

et cela ressemble à un bug.

mgcation
la source
Cela ne cesse de m'étonner à quel point certaines personnes pensent des autres. Je n'aurais jamais pensé à de telles bizarreries. Idées intéressantes.
Dr. Person Person II
1

Voir « Existe-t-il un moyen de définir des variables de deux types dans la boucle for? » Pour une autre méthode impliquant l'imbrication de plusieurs boucles. L'avantage de l'autre sens par rapport au "struct trick" de Georg est qu'il (1) vous permet d'avoir un mélange de variables locales statiques et non statiques et (2) il vous permet d'avoir des variables non copiables. L'inconvénient est qu'il est beaucoup moins lisible et peut être moins efficace.

tgoodhart
la source
-2

Définissez une macro:

#define FOR( typeX,x,valueX,  typeY,y,valueY,  condition, increments) typeX x; typeY y; for(x=valueX,y=valueY;condition;increments)

FOR(int,i,0,  int,f,0.0,  i < 5, i++)
{
  //...
}

N'oubliez pas que vos étendues variables ne seront pas non plus dans la boucle for de cette façon.

Ryan Favale
la source
Vous pouvez facilement surmonter cette limitation en enveloppant le code dans la macro dans une portée distincte à l'aide de {et }.
Nathan Osman
4
Non, il ne pouvait pas. Sa macro n'enveloppe pas le corps de la boucle. Il pourrait ajouter un support d'ouverture supplémentaire, mais cela nécessiterait un support de fermeture "supplémentaire" lors de l'utilisation de la macro.
John
3
C'est une idée intéressante, mais j'utiliserais plus tôt l'une des autres réponses avant d'envisager cela.
gregn3
-2

Vous pouvez également utiliser comme ci-dessous en C ++.

int j=3;
int i=2;
for (; i<n && j<n ; j=j+2, i=i+2){
  // your code
}
Loyola
la source