Considérez l'extrait de code:
#include <unordered_map>
void foo(const std::unordered_map<int,int> &) {}
int main()
{
foo({});
}
Cela échoue avec GCC 4.9.2 avec le message:
map2.cpp:7:19: error: converting to ‘const std::unordered_map<int, int>’ from initializer list would use explicit constructor ‘std::unordered_map<_Key, _Tp, _Hash, _Pred, _Alloc>::unordered_map(std::unordered_map<_Key, _Tp, _Hash, _Pred, _Alloc>::size_type, const hasher&, const key_equal&, const allocator_type&) [with _Key = int; _Tp = int; _Hash = std::hash<int>; _Pred = std::equal_to<int>; _Alloc = std::allocator<std::pair<const int, int> >; std::unordered_map<_Key, _Tp, _Hash, _Pred, _Alloc>::size_type = long unsigned int; std::unordered_map<_Key, _Tp, _Hash, _Pred, _Alloc>::hasher = std::hash<int>; std::unordered_map<_Key, _Tp, _Hash, _Pred, _Alloc>::key_equal = std::equal_to<int>; std::unordered_map<_Key, _Tp, _Hash, _Pred, _Alloc>::allocator_type = std::allocator<std::pair<const int, int> >]’
Test avec d'autres implémentations de compilateur/bibliothèque:
Quelques autres points déconcertants:
std::unordered_map
par std::map
fait disparaître l'erreur,foo({})
par foo foo({{}})
fait également disparaître l'erreur.De plus, le remplacement de {}
Par une liste d'initialisation non vide fonctionne comme prévu dans tous les cas.
Mes principales questions sont donc:
foo({{}})
pour que l'erreur disparaisse?MODIFIER correction de quelques fautes de frappe.
La syntaxe d'initialisation indirecte avec un braced-init-list que votre code utilise s'appelle copy-list-initialization.
La procédure de résolution de surcharge sélectionnant le meilleur constructeur viable pour ce cas est décrite dans la section suivante de la norme C++:
§ 13.3.1.7 Initialisation par initialisation de liste
[over.match.list]
Lorsque des objets de type de classe non agrégé
T
sont initialisés par liste (8.5.4), la résolution de surcharge sélectionne le constructeur en deux phases:- Initialement, les fonctions candidates sont les constructeurs de listes d'initialiseurs (8.5.4) de la classe
T
et la liste d'arguments se compose de la liste d'initialiseurs comme argument unique.- Si aucun constructeur de liste d'initialisation viable n'est trouvé, la résolution de surcharge est exécutée à nouveau, où les fonctions candidates sont tous les constructeurs de la classe
T
et la liste d'arguments se compose des éléments de la liste d'initialisation.Si la liste d'initialisation n'a aucun élément et que
T
a un constructeur par défaut, la première phase est omise. Dans la copie-liste-initialisation, si un constructeur explicite est choisi, l'initialisation est mal formée. [ Remarque: Cela diffère des autres situations (13.3.1.3, 13.3.1.4), où seuls les constructeurs de conversion sont pris en compte pour l'initialisation de la copie. Cette restriction s'applique uniquement si cette initialisation fait partie du résultat final de la résolution de surcharge. — note de fin].
Selon cela, un initializer-list-constructor (celui appelable avec un seul argument correspondant au paramètre du constructeur de type std::initializer_list<T>
) Est généralement préféré aux autres constructeurs, mais pas si un constructeur par défaut est disponible , et le braced-init-list utilisé pour list-initialization est vide .
Ce qui est important ici, l'ensemble des constructeurs des conteneurs de la bibliothèque standard a changé entre C++ 11 et C++ 14 en raison de LWG problème 2193 . Dans le cas de std::unordered_map
, Dans l'intérêt de notre analyse, nous nous intéressons à la différence suivante:
C++ 11:
explicit unordered_map(size_type n = /* impl-defined */,
const hasher& hf = hasher(),
const key_equal& eql = key_equal(),
const allocator_type& alloc = allocator_type());
unordered_map(initializer_list<value_type> il,
size_type n = /* impl-defined */,
const hasher& hf = hasher(),
const key_equal& eql = key_equal(),
const allocator_type& alloc = allocator_type());
C++ 14:
unordered_map();
explicit unordered_map(size_type n,
const hasher& hf = hasher(),
const key_equal& eql = key_equal(),
const allocator_type& alloc = allocator_type());
unordered_map(initializer_list<value_type> il,
size_type n = /* impl-defined */,
const hasher& hf = hasher(),
const key_equal& eql = key_equal(),
const allocator_type& alloc = allocator_type());
En d'autres termes, il existe un autre constructeur par défaut (celui qui peut être appelé sans arguments) en fonction de la norme de langage (C++ 11/C++ 14), et ce qui est crucial, le constructeur par défaut en C++ 14 est désormais non -explicit
.
Ce changement a été introduit pour que l'on puisse dire:
std::unordered_map<int,int> m = {};
ou:
std::unordered_map<int,int> foo()
{
return {};
}
qui sont tous deux sémantiquement équivalents à votre code (en passant {}
comme argument d'un appel de fonction pour initialiser std::unordered_map<int,int>
).
Autrement dit, dans le cas d'une bibliothèque conforme à C++ 11, l'erreur est attendue , car le constructeur sélectionné (par défaut) est explicit
, donc le code est mal formé:
explicit unordered_map(size_type n = /* impl-defined */,
const hasher& hf = hasher(),
const key_equal& eql = key_equal(),
const allocator_type& alloc = allocator_type());
Dans le cas d'une bibliothèque conforme à C++ 14, l'erreur est non attendue , car le constructeur sélectionné (par défaut) est pas explicit
, et le code est bien formé:
unordered_map();
En tant que tel, le comportement différent que vous rencontrez est uniquement lié à la version de libstdc ++ et libc ++ que vous utilisez avec différents compilateurs/options de compilateur.
Le remplacement de
std::unordered_map
Parstd::map
Fait disparaître l'erreur. Pourquoi?
Je soupçonne que c'est simplement parce que std::map
Dans la version libstdc ++ que vous utilisez a déjà été mis à jour pour C++ 14.
Le remplacement de
foo({})
parfoo({{}})
fait également disparaître l'erreur. Pourquoi?
Parce que maintenant c'est copie-liste-initialisation{{}}
Avec un non vide braced-init-list (c'est-à-dire qu'il contient un élément à l'intérieur, initialisé avec un braced-init-list{}
), donc la règle de la première phase du § 13.3.1.7 [over.match.list]/p1 (cité précédemment) qui préfère un initializer-list-constructor aux autres est appliqué. Ce constructeur n'est pas explicit
, donc l'appel est bien formé.
Le remplacement de
{}
Par une liste d'initialisation non vide fonctionne comme prévu dans tous les cas. Pourquoi?
Comme ci-dessus, la résolution de surcharge se retrouve avec la première phase du § 13.3.1.7 [over.match.list]/p1.
L'initialisation de liste pour les références est définie comme suit, [dcl.init.list]/3:
Sinon, si
T
est un type de référence, une valeur temporaire du type référencé parT
est copie-liste-initialisée ou directe-liste-initialisée, selon le type d'initialisation de la référence et la référence est liée à celle temporaire.
Votre code échoue donc car
std::unordered_map<int,int> m = {};
échoue. L'initialisation de liste pour ce cas est couverte par cette puce de [dcl.init.list]/3:
Sinon, si la liste d'initialisation ne contient aucun élément et que
T
est un type de classe avec un constructeur par défaut, l'objet est initialisé en valeur.
Ainsi, le constructeur par défaut de l'objet sera appelé1.
Passons maintenant aux bits cruciaux: en C++ 11, unordered_map
avait ce constructeur par défaut2:
explicit unordered_map(size_type n = /* some value */ ,
const hasher& hf = hasher(),
const key_equal& eql = key_equal(),
const allocator_type& a = allocator_type());
De toute évidence, appeler ce constructeur explicit
par le biais de la copie-liste-initialisation est mal formé, [over.match.list]:
Dans copy-list-initialization, si un constructeur
explicit
est choisi, l'initialisation est mal formée.
Depuis C++ 14 unordered_map
déclare un constructeur par défaut non explicite:
unordered_map();
Une implémentation de bibliothèque standard C++ 14 devrait donc compiler cela sans problème. Vraisemblablement, libc ++ est déjà mis à jour, mais libstdc ++ est à la traîne.
To value-initialize un objet de type
T
signifie:
- siT
est un type de classe (éventuellement qualifié par cv) (article 9) avec un constructeur fourni par l'utilisateur (12.1), alors le constructeur par défaut pourT
est appelé […];
2) [class.ctor]/4:
Un constructeur par défaut pour une classe
X
est un constructeur de classeX
qui peut être appelé sans argument.