Pour quiconque aime la réponse de std::views::iota
de Cigien mais qui ne fonctionne pas en C++20 ou supérieur, il est plutôt simple de mettre en œuvre une version simplifiée et légère de std::views::iota
compatible avec c++11 ou supérieur.
Tout ce dont vous avez besoin est :
- Un type de "LegacyInputIterator" de base (quelque chose qui définit
operator++
et operator*
) qui enveloppe une valeur entière (par exemple, un int
)
- Une classe de type "range" qui a des méthodes
begin()
et end()
qui renvoient les itérateurs mentionnés ci-dessus. Cela permettra de travailler dans des boucles for
basées sur les plages
Une version simplifiée de ceci pourrait être :
#include
// Il s'agit simplement d'une classe qui enveloppe un 'int' dans une abstraction d'itérateur
// Les comparaisons comparent la valeur sous-jacente, et 'operator++' incrémente simplement
// l'entier sous-jacent
class counting_iterator
{
public:
// boilerplate d'itérateur de base
using iterator_category = std::input_iterator_tag;
using value_type = int;
using reference = int;
using pointer = int*;
using difference_type = std::ptrdiff_t;
// Constructeur / affectation
constexpr explicit counting_iterator(int x) : m_value{x}{}
constexpr counting_iterator(const counting_iterator&) = default;
constexpr counting_iterator& operator=(const counting_iterator&) = default;
// "Déférencement" (renvoie simplement la valeur sous-jacente)
constexpr reference operator*() const { return m_value; }
constexpr pointer operator->() const { return &m_value; }
// Avancement de l'itérateur (incrémente simplement la valeur)
constexpr counting_iterator& operator++() {
m_value++;
return (*this);
}
constexpr counting_iterator operator++(int) {
const auto copy = (*this);
++(*this);
return copy;
}
// Comparaison
constexpr bool operator==(const counting_iterator& other) const noexcept {
return m_value == other.m_value;
}
constexpr bool operator!=(const counting_iterator& other) const noexcept {
return m_value != other.m_value;
}
private:
int m_value;
};
// Juste un type conteneur qui définit 'begin' et 'end' pour
// l'itération basée sur les plages. Cela détient le premier et le dernier élément
// (début et fin de la plage)
// L'itérateur de début est créé à partir de la première valeur, et l'itérateur de fin est créé à partir de la seconde valeur.
struct iota_range
{
int first;
int last;
constexpr counting_iterator begin() const { return counting_iterator{first}; }
constexpr counting_iterator end() const { return counting_iterator{last}; }
};
// Une fonction d'aide simple pour renvoyer la plage
// Cette fonction n'est pas strictement nécessaire, vous pourriez simplement construire
// directement l 'iota_range'
constexpr iota_range iota(int first, int last)
{
return iota_range{first, last};
}
J'ai défini le code ci-dessus avec constexpr
là où c'est supporté, mais pour les versions antérieures de C++ comme C++11/14, vous devrez peut-être supprimer constexpr
là où ce n'est pas autorisé dans ces versions.
Le code ci-dessus permet d'exécuter le code suivant en pré-C++20 :
for (int const i : iota(0, 10))
{
std::cout << i << " "; // ok
i = 42; // erreur
}
Cela générera le même assemblage que la solution C++20 std::views::iota
et la solution classique avec une boucle for
lorsqu'elle est optimisée.
Ceci fonctionne avec n'importe quel compilateur conforme à C++11 (par exemple, des compilateurs comme gcc-4.9.4
) et produit toujours un assemblage pratiquement identique à une solution de boucle for
de base.
Remarque : La fonction d'aide iota
est juste pour une parité de fonctionnalités avec la solution C++20 std::views::iota
; mais en réalité, vous pourriez aussi construire directement un iota_range{...}
au lieu d'appeler iota(...)
. Le premier présente simplement un chemin de mise à niveau facile si un utilisateur souhaite passer à C++20 à l'avenir.
4 votes
Je crois qu'il n'y a aucun moyen de faire cela.
0 votes
Vous auriez besoin de cacher la variable du corps de la boucle, peut-être changer quelque chose comme
while(i_copy = loop()) { }
0 votes
Vous pouvez faire une référence constante à celui-ci dans le corps de la boucle, par exemple
const int& i_safe = i
. Votre compilateur devrait éluder toute indirection.0 votes
@Brian Cela laisse toujours
i
vulnérable au contenu malveillant de la boucle.2 votes
Je suppose qu'une solution qui se débarrasse de
i
n'est pas bonne ? Vous voulez avoir un accès en lecture seule ài
dans la boucle ?1 votes
J'avais toujours voulu proposer
for({int i=0; i<10; ++i}){
pour cela, mais je n'ai jamais eu le courage de le suggérer au comité des normes, ou à Bjarne quand il travaille honnêtement chez Morgan Stanley. Bien sûr, ma suggestion ne vous permettrait même pas d'accéder ài
dans le corps.0 votes
Si vous restez silencieux, rien ne changera jamais. À moins qu'il ne vous fouette et vous jette dehors de l'université pour avoir fait une bonne suggestion (si c'est le cas, que faites-vous là?), alors rien ne va, rien ne vient.
28 votes
Cela semble être une solution à la recherche d'un problème.
14 votes
Transformez le corps de votre boucle for en une fonction avec un argument
const int i
. La mutabilité de l'index n'est exposée que là où elle est nécessaire et vous pouvez utiliser le mot-cléinline
pour qu'il n'ait aucun effet sur la sortie compilée.4 votes
Quoi (ou plutôt, qui) pourrait éventuellement changer la valeur de l'index à part.... vous? Vous ne vous faites pas confiance? Peut-être un collègue? Je suis d'accord avec @PeteBecker.
5 votes
@Z4-tier Oui, bien sûr, je me méfie de moi-même. Je sais que je fais des erreurs. Tout bon programmeur le sait. C'est pourquoi nous avons des choses comme
const
pour commencer.0 votes
Si vous voulez éviter les erreurs, alors vous ne devriez pas utiliser de boucles indexées du tout car elles sont sujettes aux erreurs de décalage d'un.
1 votes
En pratique, cela ne devrait jamais poser de problème. Si votre boucle est petite, il est facile de voir que
ì
est modifié dans la boucle. Si votre boucle est grande, elle devrait être refactorisée en une fonction.