Si des éléments dans std::initializer_list
sont toujours des valeurs constantes, pourquoi avons-nous une méthode modèle comme begin()/end()
et non cbegin()/cend()
? Ce nom (par convention, en comparaison avec par exemple std::vector
) pourrait suggérer que les deux std::initializer_list
pourrait retourner iterator
alors qu'ils reviennent toujours const_iterator
.
Réponse
Trop de publicités?Bien que je ne puisse pas fournir un aperçu de la raison pour laquelle cbegin()
y cend()
ne font pas partie de std::initializer_list
L'interface de l'entreprise en plus de begin()
y end()
il y a certainement de bonnes raisons pour lesquelles les deux dernières fonctions membres devraient être présentes.
L'une des raisons est, par exemple, que l'approche basée sur la gamme for
La boucle est définie par la norme C++11 précisément en termes de fonctions begin()
y end()
(Paragraphe 6.5.4/1). Par conséquent, pour qu'il soit possible de l'utiliser avec des listes d'initialisateurs, std::initializer_list
doit fournir le begin()
y end()
les fonctions des membres :
#include <utility>
#include <iostream>
int main()
{
auto l = { 1, 2, 3, 4, 5 };
for (int x : l) // Works because std::initializer_list provides
// the member functions begin() and end().
{
std::cout << x << " ";
}
}
En outre, il est logique de considérer que les fonctions membres cbegin()
y cend()
n'étaient pas présents avant C++11 : par conséquent, avoir des begin()
y end()
sur l'interface de std::initializer_list
permet de rendre les anciens algorithmes génériques écrits en termes de begin()
y end()
fonctionnent également avec les listes d'initialisation, sans nécessiter leur réécriture.
Vous écrivez :
Ces noms (par convention, en se comparant à par exemple
std::vector
) pourrait suggérer que les deuxstd::initializer_list
pourrait retourneriterator
alors qu'ils reviennent toujoursconst_iterator
.
En fait, cette analogie n'est pas très appropriée. std::vector
La fonction de l'entreprise begin()
par exemple, renvoie un iterator
lorsqu'il est invoqué sur un non const
exemple de std::vector
(c'est-à-dire mutable, dont les éléments peuvent être modifiés, ajoutés et supprimés), et une const_iterator
lorsqu'il est invoqué sur un const
(c'est-à-dire une instance immuable, dont le contenu ne peut être modifié) :
#include <vector>
#include <type_traits>
int main()
{
// A non-const vector...
std::vector<int> v = { 1, 2, 3, 4, 5 };
auto i = v.begin();
static_assert(
std::is_same<decltype(i), decltype(v)::iterator>::value,
// ^^^^^^^^
// ...non-const iterator!
"What?");
// A const vector...
std::vector<int> const vc = { 1, 2, 3, 4, 5 };
auto ic = vc.begin();
static_assert(
std::is_same<decltype(ic), decltype(vc)::const_iterator>::value,
// ^^^^^^^^^^^^^^
// ...const iterator!
"What?");
}
Les listes d'initialisation sont des collections immuables par définition. Paragraphe 18.9/2 de la norme C++11 :
Un objet de type
initializer_list<E>
permet d'accéder à un tableau d'objets de typeconst E
. [...]
Puisque les listes d'initialisation sont des collections de const
les éléments cbegin()
y cend()
ferait en fait exactement la même chose que begin()
y end()
faire.
En fait, iterator
y const_iterator
sont toutes deux définies comme des pointeurs vers des éléments constants du type de valeur de la liste d'initialisation, on peut donc se demander si c'est le cas que begin()
y end()
toujours revenir const_iterator
(comme vous le supposez), ou si elles retournent toujours iterator
.
C'est ainsi que le paragraphe 18.9/1 de la norme C++11 définit l'approche de l'utilisateur. initializer_list
modèle de classe :
namespace std {
template<class E> class initializer_list {
public:
typedef E value_type;
// ...
typedef const E* iterator;
typedef const E* const_iterator;
// ...
constexpr const E* begin() const noexcept; // first element
constexpr const E* end() const noexcept; // one past the last element
};
// ...
}